1. Filter와 Interceptor 개념

  • Filter와 Interceptor은 공통적인 관심 사항이 있을 때 사용합니다.
  • 공통적인 관심 사항 : 여러 로직에서 공통으로 관심있는 관심사
  • Filter와 Interceptor는 HttpServletRequest를 제공합니다.
  • 예) 로그인을 안하면 회원 수정, 삭제, 게시물 삭제, 등록 등을 보지를 못합니다. 여기서 공통으로 로그인에 대한 인증을 관심을 가지고 있습니다. 이 관심사를 Filter와 Interceptor로 해결할 수 있습니다.

2. 서블릿 Filter

  • 필터의 흐름

적절한 요청이 왔을때 : Http 요청 -> 서버 -> 필터 -> 서블릿 -> 컨트롤러

적절하지 않은 요청이 왔을 때 : Http요청 -> 서버 -> 필터 -> X

  • 적절한 요청이 왔을때는 컨트롤러를 호출하는데 적절한 요청이 오지 않았을 경우 필터에서 서블릿을 호출하지 않습니다.
  • 필터 체인

필터의 흐름에서 필터를 자유롭게 추가할 수 있습니다.

  • 필터 체인 예시 : Http 요청 -> 서버 -> 필터 -> 필터 -> 필터 -> 서블릿 -> 컨트롤러

 

 

필터 구현

// Filter

public FilterEx implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                                                       throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        try{
            log.info("REQUEST [{}][{}]", uuid, requestURI);
            chain.doFilter(request,response);
        }catch (Exception e) {
            throw e;
        }finally {
            log.info("RESPONSE [{}][{}]", uuid, requestURI);
        }
    }


     @Override
    public void destroy() {
    }



}
  • 필터를 사용할려면 Filter 인터페이스를 구현해야 합니다.
  • init : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출됩니다.
  • destroy : 필터 종료 메서드 , 서블릿 컨테이너가 종료될 때 호출됩니다.
  • doFilter : 요청이 왔을 때 호출된다.
  • HttpServletRequest httpRequest = (HttpServletRequest) request : ServletRequest는 Http 요청이 아닌 것까지 고려해서 만든 것이라 HttpServletRequest로 다운 캐스팅을 해준다.
  • chain.doFilter(request, response) : 다음 필터가 있으면 다음 필터를 호출하고 없으면 서블릿을 호출한다.

* 참고 : init과 destroy 메서드는 구현 안 해줘도 됩니다. 왜냐하면 Filter에서 default로 메서드를 정의해 놓았기 때문입니다.

 

 

필터 등록

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new FilterEx()); // 필터 등록
        filterFilterRegistrationBean.setOrder(1); // 순서 => 숫자가 작을수로 우선순위 높음
        filterFilterRegistrationBean.addUrlPatterns("/*"); // /* 모든 경로
        return filterFilterRegistrationBean;

    }
}
  • 스프링 부트를 사용할 경우 FilterRegistrationBean를 사용해서 필터를 등록해주면 됩니다.
  • setFilter(new FilterEx()) : 구현한 필터를 등록합니다.
  • setOrder(1) : 필터 순서를 설정해줍니다.(숫자가 낮을수록 우선순위가 높습니다.)
  • addUrlPatterns("/*") : 필터를 적용할 경로를 설정해줍니다.

 

3. 스프링 Interceptor

  • Interceptor 흐름

적절한 요청이 왔을 때 : Http 요청 -> 서버 -> 필터 -> 서블릿 -> Interceptor -> 컨트롤러

적절하지 않은 요청이 왔을때 : Http 요청 -> 서버 -> 필터 -> 서블릿 -> Interceptor -> x

  • 적절한 요청이 오면 Interceptor에서 컨트롤러를 호출하는데 적절하지 않은 요청은 컨트롤러를 호출하지 않는다.

 

  • Interceptor 체인

필터와 마찬가지로 Interceptor를 중간에 자유롭게 추가할 수 있다.

  • Http 요청 -> 서버 -> 필터 -> 서블릿 -> Interceptor -> Interceptor -> Interceptor -> 컨트롤러

 

  • Interceptor 호출 흐름

 

  1. preHandle() 호출 
  2. handle 반환
  3. ModelAndView 반환
  4. postHandle() 호출
  5. render 호출
  6. afterCompletion() 호출

 

Interceptor 구현

public class InterceptorEx implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        request.setAttribute("logId", 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("logId");
        log.info("RESPONSE [{}][{}]", logId, requestURI);
        if(ex !=null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

 

  • HandlerInterceptor 인터페이스를 구현해야 합니다.
  • preHandle : 컨트롤러 호출 전에 호출됩니다.
  • preHandle return 이 false 이면 preHandle 호출하는 부분에서 끝나고 true이면 컨트롤러를 호출합니다.
  • postHandle : 컨트롤러 호출 후에 호출됩니다.
  • 예외가 발생하면 postHandle를 호출되지 않는다.
  • afterCompletion : 뷰 랜더링 후에 호출됩니다.
  • 예외가 발생해도 afterCompletion은 호출됩니다.

 

 

 

Interceptor 등록

 

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptorEx())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**","/*.ico","/error");
        }
}
  • addInterceptor(new InterceptorEx()) : 구현한 Interceptor를 등록합니다.
  • order(int order) : Interceptor의 우선순위를 설정합니다.( 숫자가 낮을수록 우선순위가 높습니다.)
  • addPathPatterns("/**") : Interceptor를 적용할 경로를 지정합니다.(/** => 전체 경로)
  • excludePathPatterns("/css/**", "/*. ico", "/error") : Interceptor에서 제외할 패턴을 지정합니다.

 

정리

인터셉터는 스프링 MVC 구조에 특화된 필터 기능을 제공합니다. 스프링 MVC를 사용하고, 특별히 필터를  사용해야 하는 상황이 아니라면 인터셉터를 사용하는 것이 더 편리합니다.