모든 핸들러에서 로그인 여부 확인
로직을 수행해야 한다.
그렇다고 로그인 여부 확인
코드를 모든 핸들러에 넣을 것인가?
Servlet의 Filter
와 Spring Interceptor
로 해결해보자
WAS에서 Servlet 객체를 호출하기 전, Filter를 호출할 수 있다.
Spring Web에서는 DispatcherServlet이 그림의 서블릿에 해당
public interface Filter {
// 필터 객체 생성시 호출
public default void init(FilterConfig filterConfig) throws ServletException {}
// 필터 객체의 메인 로직이 정의되는 곳
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;
// 필터 객체 소멸시 호출 (보통 서버 종료시)
public default void destroy() {}
}
용도에 맞게 Filter 인터페이스를
구현한 구현체를 Bean으로 등록하면 된다.
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import javax.servlet.Filter;
@Configuration
public class Config {
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
// 사용할 필터를 설정
filterRegistrationBean.setFilter(new MyFilter());
// 필터 적용 순서를 지정
filterRegistrationBean.setOrder(1);
// 필터가 반응할 URL을 지정(모든 요청에 대해 반응)
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
@WebFilter
, Spring의 @ServletComponentScan
을 통해 서블릿, 필터, 리스너를 등록할 수 있으나 필터 적용 순서를 지정하지 못한다는 단점이 있다.동일한 기능을 Spring Interceptor
로 구현할 수 있다.
HTTP 요청 (클라이언트) -> WAS (Tomcat) -> 필터 (서블릿) -> 서블릿 (DispatcherServlet) -> 'Interceptor' -> Controller
인터셉터도 필터와 마찬가지로 Chaining이 가능하다
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
public interface HandlerInterceptor {
// 컨트롤러 호출 전 (정확히는 '핸들러 어댑터' 호출 전)
// 반환값이 true -> 다음 인터셉터를 호출
// 반환값이 false -> 진행 X (핸들러 어댑터, 컨트롤러 호출 X)
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
// 컨트롤러 호출 후 (정확히는 '핸들러 어댑터' 호출 후)
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
// 응답 완료 후 (View가 Rendering 된 이후)
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
서블릿 필터와 마찬가지로, 용도에 맞게 HandlerInterceptor 인터페이스를
구현한 구현체를 Bean으로 등록하면 된다.
preHandle에서 request.setAttribute를 통해 postHandle, afterCompletion으로 데이터를 넘길 수 있다.
(인터셉터는 싱글톤처럼 사용되고, 멀티 스레드 환경을 측면에서 멤버 변수를 사용하면 위험하다.)
서블릿 필터는 단순히 요청이 발생하고 컨트롤러를 호출하기 직전에 doFilter
를 한 번만 호출 할 수 있지만,
스프링 인터셉터는 컨트롤러 호출 전 (preHandle)
, 컨트롤러 호출 후 (postHandle)
, 응답 완료 후 (afterCompletion)
중 원하는 순간에 인터셉터를 호출할 수 있도록 했다.
서블릿 필터는 ServletRequest
, ServletResponse
만 다루는 반면, 스프링 인터셉터는 Handler
, ModelAndView
, Exception
을 다룰 수 있다.
`Handler`
호출할 Controller의 메서드
요청에 따라 Handler 종류가 달라진다
(복습 필요,,,)
서블릿 필터는 chain.doFilter()
를 명시적으로 호출해야하는 반면, Interceptor는 그럴 필요 없다는 장점이 있다.
Controller에서 예외가 발생하는 경우 PostHandle
은 호출되지 않는다.
DispatcherServlet에서 예외를 전달
Controller에서 예외가 발생하더라도 afterCompletion
은 항상 호출된다.
예외가 발생하지 않으면 파라미터로 넘어온 Exception == null
즉, 모든 상황에서 작동하길 바라는 종료 작업은 PostHandle이 아닌 afterCompletion에서 수행해야 한다.
HandlerInterceptor 구현체
를 등록하는 방법은 약간은 다르다.
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@Configuration
public class Config implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**") // 서블릿의 url 패턴과 약간 다르다 (모든 경로에 대해 적용)
.excludePatterns("/css/**", "/*.ico", "/error"); // (특정 경로는 제외)
}
}
다음을 참고하자
공부 후 추후 정리,,,