스프링 #12 필터, 인터셉터

함형주·2023년 1월 7일
0

spring

목록 보기
12/12

질문, 피드백 등 모든 댓글 환영합니다.

스프링에서 자주 사용되는 필터와 인터셉터에 대해 알아보겠습니다.

필터와 인터셉터는 스프링 MVC에서 공통 관심 사항을 처리하기 위해 사용합니다.

예를 들어 대부분의 서비스는 로그인 이용자와 비로그인 이용자가 접근할 수 있는 서비스가 다릅니다. 로그인 여부를 떠나서도 사용자의 나이, 등급, 위치 등 여러 조건에 따라 서비스의 범위가 제한됩니다.

물론 이를 컨트롤러에서 일일히 적용해도 되긴 하지만 모든 메서드에 적용하기엔 너무나 많은 노력이 필요하며 만약 해당 로직에 변경 사항이 발생한다면...

그리고 스프링의 이념인 객체지향적으로 생각했을 때에도 바람직하지 못합니다.

공통 관심 사항을 처리하는 것에는 여러 방식이 있지만 특히 web 계층에서 발생하는 부분은 주로 필터와 인터셉터를 사용합니다.

둘 사이에 차이점이 있다면 필터는 서블릿에서 제공하는 기능이고 인터셉터는 스프링이 제공하는 기능입니다.

필터

필터는 서블릿이 제공하는 기능입니다.

WAS에 HTTP 요청이 발생했을 때 서블릿이 호출 되기 전에 필터로 해당 요청을 읽을 수 있습니다.

HTTP 요청 경로 : HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 컨트롤러

필터에서 만약 적절하지 않은 요청이라 판단하면 서블릿을 거치지 않고 해당 요청을 반려할 수 있으므로 위에서 언급한 web 계층에서의 공통 관심 사항을 처리하기 용의합니다.

필터는 여러개를 체인 형식으로 등록할 수 있으며 순서를 지정하여 사용할 수 있습니다.

필터에는 3개의 메서드가 있습니다.

  • void init(FilterConfig filterConfig) : 서블릿 컨테이너가 생성될 때 호출
  • void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) :
    요청이 발생하면 호출
  • void destroy() : 서블릿 컨테이너가 종료될 때 호출

필터는 Filter(Interface)를 상속 받아 생성하여 사용할 수 있습니다.

doFilter()는 파라미터로 ServletRequest를 받아 요청을 처리하며 HTTP 요청 외에도 다른 요청을 처리할 수 있으며 HttpServletRequest를 사용하려면 다운 캐스팅 하여 사용할 수 있습니다.

CustomFilter

@Slf4j
public class CustomFilter implements Filter {

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
    	log.info("CustomFilter init()");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
    FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info("doFilter : REQUEST : {}", requestURI);
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
    log.info("CustomFilter destroy()");
    }
}

위와 같이 Filter를 구현해서 사용할 수 있고 등록할 때에는 FilterRegistrationBean을 스프링 빈으로 등록하여 사용합니다.

@Bean
public FilterRegistrationBean logFilter() {
    FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
    filterRegistrationBean.setFilter(new CustomFilter());
    filterRegistrationBean.setOrder(1);
    filterRegistrationBean.addUrlPatterns("/*");
    return filterRegistrationBean;
}

FilterRegistrationBean를 빈으로 등록하여 Filter를 등록하고 순서, 적용 경로 등을 설정할 수 있습니다.

필터를 적절히 이용하면 맨 위에서 언급했듯이 공통 관심 사항을 처리할 수 있어 한 곳에서 해당 로직을 관리할 수 있습니다.

인터셉터

위에서 언급했듯이 필터와 인터셉터의 차이는 인터셉터는 스프링이 제공하는 기능이라는 점입니다.

인터셉터는 스프링에서 제공하기에 서블릿에서 제공하는 필터와 적용되는 위치가 다릅니다.

HTTP 요청 경로 : HTTP 요청 -> WAS -> 필터 -> 디스패처 서블릿 -> 인터셉터 -> 컨트롤러

스프링이 제공하므로 디스패처 서블릿 이후에 적용할 수 있습니다.

필터와 마찬가지로 적절하지 않은 요청이라 판단되는 경우 컨트롤러를 호출하지 않고 요청을 반려할 수 있으며 체인 형식으로 여러개의 인터셉터를 등록할 수 있습니다..

또한 스프링에서 제공하기 때문에 스프링에 좀 더 유용한 기능을 제공합니다.

인터셉터는 HandlerInterceptor(interface)를 구현하여 사용할 수 있습니다.

필터의 경우엔 doFilter() 하나의 메서드로 요청을 처리했지만 인터셉터는 3개의 메서드를 제공합니다.

  • boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) : 컨트롤러 호출 전에 호출 (정확히는 핸들러 어댑터 호출 전) 반환 값이 ture면 정상 진행, false면 이후 진행 x
  • void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView)
    : 컨트롤러 호출 이후 호출 (정확히는 핸들러 어댑터 호출 이후)
  • void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) : 뷰 렌더링 이후 호출

인터셉터는 3가지 메서드로 인터셉터 호출 시점을 세분화 하여 적용할 수 있습니다.

호출 시점을 조절할 수 있을 뿐더러 예외를 컨트롤 할 수도 있습니다.

preHandle()의 경우에는 컨트롤러 호출 이전에 호출되므로 예외와는 크게 관련이 없고 postHandle()의 경우에는 컨트롤러 이후 로직에서 예외 발생 시 호출되지 않습니다.

afterCompletion()의 경우에는 도중에 서버와 완전히 죽어버리지 않는 이상 항상 호출되며 파라미터로 Exception을 받아서 이를 핸들링 할 수 있습니다.

또한 파라미터로 Object handler를 사용하는데 만약 @RequestMapping 방식의 컨트롤러를 사용한다면(대부분의 방식) handler를 HandlerMethod로 다운 캐스팅하여 사용하면 이후 호출되는 컨트롤러와 그 메서드에 대한 정보를 얻을 수 있습니다.

CustomInterceptor

@Slf4j
public class CustomInterceptor implements HandlerInterceptor {
    
	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse
    response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("preHandle() : REQUEST : {}, HANDLER {}", requestURI, handler);
        return true; //false 진행X
    }
    
    @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 {
        log.info("afterCompletion()", );
        if (ex != null) {
        	log.error("afterCompletion() : error : {}", ex);
        }
    }
}

모든 메서드를 구현할 필요는 없으며 필요한 메서드만 구현하여 사용할 수 있습니다.

인터셉터는 WebMvcConfigurer를 상속 받은 클래스에서 addInterceptors()를 오버라이딩하여 등록할 수 있습니다.

Configurer (WebMvcConfigurer 상속)

public class Configurer implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CustomInterceptor_1())
                .order(1)
                .addPathPatterns("/custom1/**")
                .excludePathPatterns("/**");

        registry.addInterceptor(new CustomInterceptor_2())
                .order(2)
                .addPathPatterns("/custom2/**")
                .excludePathPatterns("/**");
    }
}

위와 같이 인터셉터를 등록할 수 있으며 한 번에 여러 인터셉터를 등록할 수 있습니다.

또한 필터와는 다르게 excludePathPatterns()addPathPatterns()를 사용하여 인터셉터가 적용될 경로와 적용되지 않을 경로를 세밀하게 설정할 수 있습니다.

profile
평범한 대학생의 공부 일기?

0개의 댓글