인터셉터에 대해서 정리하려고 합니다.
배우면서 생기는 궁금증들을 바탕으로 작성하려고 노력했습니다.
마찬가지로 수업을 배울 때 항상 실무자들은 어떻게 구조를 만드는지
눈여겨 보려고 노력합니다. 오늘 배운 부분도 구조를 분석합니다.
이번 수업에서는 Filter와 interceptor에 대해서 배웠고 둘은 web 폴더 아래 생성되어져 있습니다.
또한 ArgumentResolver 활용에 대해서 배웠습니다.
사실 여기서 들었던 의문점은 Filter를 Bean으로 등록한다고 했는데
교재에는 서블릿은 서블릿이 제공하는 기술이고 스프링 인터셉터는 스프링 MVC가 제공하는 기술이라고 한다.
어떻게 서블릿에서 제공하는 기술이 스프링 컨테이너에 빈으로 등록될 수 있는걸까?
나와 같은 의문을 가진 분이 계셨고
아주 친절하게 그 이유를 설명해두셨다.
DelegatingFilterProxy의 등장으로 가능하게 된것이다!
원래는 불가능했던 일이었다고 한다.
필터에서도 DI와 같은 스프링 기술이 필요했고 스프링 빈으로 주입받을 수 있는 대안으로 나온 것이 DelegatingFilterProxy
동작원리
다시 돌아와서
스프링 인터셉터의 흐름
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
필터와 다르게 매우 정밀하게 URL 패턴을 설정할 수 있다.
스프링 인터셉터 제한
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 -> 컨트롤러
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 스프링 인터셉터 // 비로그인
스프링 인터셉터 체인
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터1 -> 인터셉터 2 -> 컨트롤러
스프링 공식 문서에 따르면
Workflow interface that allows for customized handler execution chains. Applications can register any number of existing or custom interceptors for certain groups of handlers, to add common preprocessing behavior without needing to modify each handler implementation.
" 사용자 정의된 처리기 실행 체인을 허용하는 워크플로 인터페이스. 응용 프로그램은 특정 핸들러 그룹에 대해 기존 또는 사용자 지정 인터셉트를 수에 상관없이 등록하여 각 핸들러 구현을 수정할 필요 없이 공통 전처리 동작을 추가할 수 있습니다. "라고 파파고가 해석해줬다.
3가지의 메서드로 구성되어 있다.
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
결국은 스프링 MVC에 특화된 기술이라는 것을 확인할 수 있다.
따라서 필터를 사용해야 하는 경우가 아니라면 인터셉터를 사용할것!
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
log.info("REQUEST [{}][{}][{}]",uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response
, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response,
Object handler , Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String)request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if( ex !=null){
log.error("afterCompletion error!!",ex);
}
}
}
여기서 눈여겨 보아할 부분은 호출 시점이 분리된 인터셉터의 3가지 메소드들이 값을 공유하기 위해서 선택한 방법이 request.setAttribute(LOG_ID, uuid))
라는 것이다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/*.ico","error");
}
...
}
등록하는 방법
addInterceptors()
를 사용해서 인터셉터를 등록addPathPatterns("/**")
: 인터셉터를 적용한 URL 패턴을 지정excludePathPatterns("/css/**","/*.ico","/error")
: 인터셉터에서 제외할 패턴 지정WebMvcConfigurer에 대해 궁금해서 찾아보니
Spring MVC에서 아주 유용하게 사용되는 기능들이 선언
모든 메서드들이 default로 선언되어 있어 반드시 재정의할 필요없이 필요할 때만 재정의
그 중에서 예로 들 것은 addViewContollers이다.
스프링은 간단한 페이지를 보여주려해도 Controller가 필요하다.
addViewControllers는 Controller를 거치지 않고 바로 페이지를 보여준다.//스프링 @GetMapping("/login") public String loginForm(Model model){ return "login"; } //WebMvcController public class MVCConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRestry registry){ registry.addViewController("/login").setViewName("login"); } }
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("인증 체크 인터셉터 실행 {}", requestURI);
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(SessionConst.LOGIN_MEMBER)==null){
log.info("미인증 사용자 요청");
response.sendRedirect("/login?redirectURL="+requestURI);
return false;
}
return true;
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/*.ico","error");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns(
"/","/members/add","/login","/logout",
"/css/**","/*.ico","/error"
);
}
두 번째로 접근해야 하므로 order(2)
서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심 해결하기 위한 기술.
스프링 인터셉터가 더 편리하므로 특별한 문제가 없으면 인터셉터 사용!