지난 포스팅에서 공통 관심사 로직을 처리를 위한 필터(Filter)에 대해서 다루어 보았다. 스프링도 필터와 같은 기능을 하는 모듈을 제공하는데, 그것이 인터셉터(Interceptor)이다.
인터셉터(Interceptor)
HTTP 요청이 들어오면 위 그림과 같은 순서로 처리된다.
인터셉터는 서블릿과 컨트롤러 사이에 위치하여 공통관심사를 처리한다. 관심사별로 여러 인터셉터가 존재하는데, 필터와 같이 체인구조가 가능하여 여러 인터셉터가 정해진 순서로 처리가 된다.
필터와 다른 점이 있다면 HTTP 요청이 들어왔을 때, 세가지 메소드가 동작한다는 점이다. 필터는 doFilter 메소드 하나만 동작했다. 인터셉터는 컨트롤러가 호출되기 전에 한번, 호출이 마무리 된 후에 한번, HTTP 요청이 완료된 후에 한번, 총 3번 메소드가 호출된다.
1) preHandle 메소드 : 컨트롤러 호출되기 전 ( 핸들러 어댑터가 호출 되기 전 )
2) postHandle : 컨트롤러 호출이 마무리 된 후 ( 핸들러 어댑터가 ModelAndView 반환한 후 )
3) afterCompletion : HTTP 요청이 완료된 후 ( 뷰 렌더링이 완료된 후 )
인터셉터는 세가지 메소드가 각각 호출되는 시점도 다르기에, 시점에 따른 공통로직 처리가 가능해진다. afterCompletion은 Exception이 발생되는 상황에도 호출된다는점이 특징이다. 이는 나중에 자세히 다루어 보겠다. 이처럼 인터셉터는 SpringMVC에 특화된 구조로 공통관심사를 처리한다.
그럼 인터셉터를 등록해보자.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 공통 로그 출력 인터셉터
registry.addInterceptor(new LoginInterceptor())
.order(1) // 체인 순서 (1)
.addPathPatterns("/**") // 매핑되는 URL 패턴
.excludePathPatterns("/css/**","/*.ico","/error");// 예외 URL 패턴
// 로그인 체크 인터셉터
registry.addInterceptor(new LoginCheckInterceptor())
.order(2) // 체인 순서 (2)
.addPathPatterns("/**") // 매핑되는 URL 패턴
.excludePathPatterns("/","/members/add","/login",
"/logout","/css/**","/*.ico","/error"); // 예외 URL 패턴
}
}
인터셉터를 등록하려면 설정클래스를 만들어야 한다. WebMvcConfigurer 인터페이스는 Spring MVC와 관련된 객체를 등록할 수 있는 기능을 제공한다. 인터셉터를 등록하려면 addInterceptors 메소드를 구현하면 된다. 파라미터로 넘어온 InterceptorRegistry 객체에 인터셉터를 등록해보자.
인터셉터는 두 개를 등록하였다.
1) 공통로그 출력 인터셉터
2) 로그인 체크 인터셉터
인터셉터 설정은 메소드 체이닝 방식으로 하여 가독성이 좋다. 체인 순서, 매핑되는 URL 패턴 그리고 예외 URL 패턴 등을 설정할 수 있다. 필터를 등록할 때는 예외 URL 패턴을 설정할 수 없어, 필터 내부로직에 구현해야 했다. 이렇듯, 인터셉터 등록은 필터 등록보다 정교한 URL 설정이 가능하다.
인터셉터의 URL 패턴과 관련해서는 위 포스팅을 참고하면 된다.
그럼 인터셉터 코드를 보자.
LogInterceptor
@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 {
// 1. 요청에 매핑되는 요청ID 랜덤하게 생성
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID,uuid); // UUID Request 객체에 저장
// 2. 핸들러(컨트롤러) 정보 가져오기
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
}
// 3. 공통 로그 출력 ( preHandler )
log.info("REQUEST [{}][{}][{}]",uuid,request,handler);
// 4. true 반환시, 다음 인터셉터나 핸들러(컨트롤러) 호출
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 1. 요청 객체에서 데이터 가져오기
String logId = (String)request.getAttribute(LOG_ID);
// 2. 공통 로그 출력 ( postHandler )
log.info("postHandle [{}][{}]",logId,modelAndView); // 핸들러 호출 결과 (ModelAndView)
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 1. 요청 객체에서 데이터 가져오기
String requestURI = request.getRequestURI();
String logId = (String)request.getAttribute(LOG_ID);
// 2. 공통 로그 출력 ( afterCompletion )
log.info("RESPONSE [{}][{}][{}]",logId,request,handler);
// 3. 에러가 발생한 경우
if(ex != null){
log.error("afterCompletion error!!",ex);
}
}
}
LogInterceptor의 목적은 컨트롤러 호출 전, 컨트롤러 호출 후, HTTP 요청 완료 후 공통 로그를 출력하는 것이다.
- preHandle 메소드
컨트롤러가 호출되기 전에 호출되는 메소드이다. preHandle의 파라미터를 보면 다음과 같다.
1) HttpServletRequest request
2) HttpServletResponse response
3) Object handler
여기서 handler는 앞으로 호출할 Controller를 의미한다. handler 객체 안에는 앞으로 호출할 Controller의 데이터가 저장되어 있다. 그러므로 Controller의 데이터에 접근해서 유의미한 로그를 남기거나 공통 로직을 처리할 수 있다.
위 코드에서 preHandle 메소드는 공통로그를 남긴다. HTTP 요청에 따라 로그를 남기는 것이므로 HTTP 요청과 매핑되는 ID를 랜덤하게 생성한다. (UUID) 그리고 이를 HttpServletRequest 객체에 저장한다. ( setAttribute 메소드 ) Request 객체에 저장해놓으면 postHandle이나 afterCompletion에서 꺼내어 사용할 수 있다. 그러므로 postHandle에도, afterCompletion에도 동일한 요청ID를 로그에 남길 수 있게 된다.
- postHandle 메소드
컨트롤러 호출이 마무리 된후 호출되는 메소드이다. 정확히 말하면 컨트롤러가 디스패처 서블릿에 ModelAndView를 반환한 뒤에 호출되는 메소드이다. ModelAndView에는 컨트롤러 로직이 처리된 결과 데이터가 담겨져 있다.
1) HttpServletRequest request
2) HttpServletResponse response
3) Object handler
4) ModelAndView modelAndView
그래서 파라미터에 ModelAndView 객체가 추가되었다. ModelAndView 객체에 저장된 컨트롤러 처리 결과 데이터에 접근하여 유의미한 로그를 남기거나 공통로직을 처리할 수 있다.
- afterCompletion 메소드
HTTP 요청이 완료된 후에 호출되는 메소드이다. 뷰템플릿에서 뷰 렌더링을 완료한 후에 호출되는 메소드로 HTTP 요청 완료시점의 공통로직을 처리할 수 있다. 특이한 점은 파라미터로 Exception 객체가 넘어온다는 점이다.
1) HttpServletRequest request
2) HttpServletResponse response
3) Object handler
4) Exception ex
앞에서도 말했지만, afterCompletion은 HTTP 요청 처리 중 Exception이 발생해도 반드시 호출되는 메소드이다. Exception 객체에는 에러에 대한 정보를 가지고 있다. 그러므로 afterCompletion 메소드에서는 Exception을 핸들링하는 공통로직을 작성할 수 있다.
이렇듯, 인터셉터는 필터와 다르게 다양한 시점에서 다양한 메소드로 공통 로직을 처리할 수 있게 설계되어 있다. SpringMVC와 긴밀하게 연계되어 필요한 WEB 관련 데이터에 접근이 가능하다. 또한 필터보다 설정하는 과정도 단순하고 편리하니 특별한 경우가 아니라면 인터셉터가 주로 사용된다.
참고자료
'SPRING > Spring MVC' 카테고리의 다른 글
[SpringMVC] 오류페이지 띄우기 (0) | 2023.09.01 |
---|---|
[SpringMVC] ArgumentResolver 활용하기 (0) | 2023.08.31 |
[SpringMVC] 서블릿 필터(Filter)란? (0) | 2023.08.31 |
[SpringMVC] 서블릿 세션(Servlet Session) 이용하기 (0) | 2023.08.30 |
[SpringMVC] Session( 세션 )이란? (0) | 2023.08.30 |