티스토리 뷰

필터

필터는 서블릿이 지원하는 수문장입니다. 즉 서블릿으로 진행하는 조건을 판단할 수 있다는 것입니다.

먼저 필터의 흐름은 다음과 같습니다

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러

필터를 적용하면 필터가 호출된 다음에 서블릿이 호출됩니다. 그래서 모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 모든 요청 로그를 남길 수 있습니다. 스프링을 사용하는 경우 여기서 말하는 서블릿은 dispatcherServlet 으로 생각하면 됩니다.

 

필터에서 적절하지 않은 요청이라고 판단되면 거기에서 끝을 낼 수도 있습니다. 즉 필터에서 서블릿으로 더 이상 요청을 전달하지 않는다는 것입니다.

예를 들어 로그인 여부를 체크하는 경우 필터에서 판단하여 로그인 한 사용자라면 컨트롤러까지 요청을 보내고, 로그인을 하지 않은 사용자라면 필터에서 더 이상 전달하지 않으면 됩니다.

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 컨트롤러 (정상적인 흐름) // 로그인 사용자
HTTP 요청 -> WAS -> 필터(적절하지 않은 요청, 더 이상 진행 X) // 비로그인

필터는 체인으로도 구성할 수 있습니다. 즉 필터를 (필터 1 -> 필터 2 -> 필터 3 ....) 이런 식으로 추가할 수 있다는 것입니다.

예를들어 로그를 남기는 필터를 먼저 적용하고, 그 다음으로 로그인 여부를 판단하는 필터를 적용시킬 수 있습니다.

 

 

자바에서 제공하는 필터 인터페이스를 살펴보겠습니다 !

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() {}
   }

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고 관리합니다.

필터를 사용하기 위해서는 당연히 이 인터페이스(필터)를 구현해야겠죠?! 😀

  • init(): 필터를 초기화 하는 함수입니다. 서블릿 컨테이너가 생성될 때 호출됩니다.
  • doFilter(): 고객의 요청이 올 때 마다 호출되는 함수입니다. 필터의 로직을 구현하는 핵심 함수입니다.
  • destroy(): 필터를 종료하는 함수입니다. 서블릿 컨테이너가 종료될 때 호출됩니다.

 

여기서 한 가지 살펴볼 점은 doFilter 함수입니다. 해당 함수에서 넘어오는 파라미터는 HttpServletRequest가 아닌 ServletRequest 입니다. 즉 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스라는 것입니다. 그러므로 HTTP를 사용하면 HttpServletRequest로 형변환을 해주셔야합니다. 그리고 doFilter 함수에서는 반드시 chain.doFilter(request, response)를 호출해야합니다. 필터가 체인으로 구성되어 있는데 해당 구문이 빠지게 된다면 다음 필터가 호출되지 않습니다.

 

마지막으로 필터를 사용하기 위해서는 필터를 등록해야 합니다. 스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록하면 됩니다. 예제 코드를 살펴보겠습니다.

@Configuration
    public class WebConfig {
        @Bean
        public FilterRegistrationBean ourFilter() {
            FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
            
            filterRegistrationBean.setFilter(new ourFilter());
            filterRegistrationBean.setOrder(1);
            filterRegistrationBean.addUrlPatterns("/*");
            return filterRegistrationBean;
	}
}
  • setFilter(): 등록할 필터를 지정합니다. ourFilter가 해당되고 ourFilter는 filter를 구현하는 클래스로 구현이 되어있어야합니다.
  • setOrder(): 필터는 체인으로 동작합니다. 여기서 1은 우선순위라고 생각하면 됩니다. 낮을 수록 먼저 동작합니다.
  • addUrlPatterns(): 필터를 적용할 URL 패턴을 지정합니다.

 


인터셉터

스프링 인터셉터도 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 해결할 수 있는 기술이다. 인터셉터는 스프링 MVC가 제공하는 기술이다. 인터셉트의 흐름은 다음과 같다

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러

인터셉터는 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출됩니다. 스프링 MVC가 제공하는 기능이기 때문에 결국 dispatcherServlet 이후에 등장하게 됩니다. 인터셉터도 URL 패턴을 적용할 수 있는데, 서블릿 URL 패턴과는 다르고 매우 정밀하게 설정할 수 있습니다.

 

필터와 마찬가지로 적절한 판단, 적절하지 않은 판단을 내릴 수 있습니다.

HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러 (정상적인 흐름) //로그인 사용자
HTTP 요청 -> WAS -> 필터 -> 서블릿 -> 인터셉터(적절하지 않은 요청이라 판단, 컨트롤러 호출 X) // 비 로그인 사용자

인터셉터도 필터와 마찬가지로 체인으로 구성할 수 있습니다. 예를 들어 로그를 남기는 인터셉트를 먼저 적용하고, 그 다음에 로그인 여부를 체크하는 인터셉터를 만들 수 있습니다.

 

스프링에서 제공하는 인터셉터 인터페이스를 살펴보겠습니다 !

public interface HandlerInterceptor {

	
	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 {
	}

	
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

서블릿 필터의 경우 위에서 본 것 처럼 doFilter 함수 하나만 제공되어 해당 함수 내에서 로직을 처리해야 했습니다.

인터셉트는 컨트롤러의 호출 시기에 따라 세분화 된 함수를 제공합니다.

  • preHandle(): 컨트롤러가 호출되기 전에 실행되는 함수입니다. 응답값이 true이면 다음으로 진행하고 false이면 진행하지 않습니다.
  • postHandler(): 컨트롤러가 호출된 이후에 실행되는 함수입니다. 
  • afterCompletion(): 요청이 완료된 이후에 실행되는 함수입니다. 뷰가 렌더링 된 이후에 호출됩니다.

또 필터의 경우 request, response만 제공했지만, 인터셉터는 어떤 컨트롤러가 호출되는지(handler 인자) 호출 정보도 받을 수 있습니다. 그리고 어떤 modelAndView가 반환되는지 응답 정보도 받을 수 있습니다.

예외가 발생하는 경우

  • preHandle(): 컨트롤러 호출 전에 호출되므로 반드시 실행이 됩니다.
  • postHandler(): 컨트롤러에서 예외가 발생되면 호출되지 않습니다.
  • afterCompletion(): 항상 호출이 되는 함수입니다. 이 경우 ex를 인자로 받아서 어떤 예외가 발생했는지 알 수 있습니다. 예외와 무관하게 공통 처리를 하려면 반드시 이 함수에서 로직을 실행해야합니다.

마지막으로 필터와 마찬가지로 인터셉터를 사용하기 위해서는  등록해야 합니다. 스프링 부트를 사용한다면 FilterRegistrationBean을 사용해서 등록하면 됩니다. 예제 코드를 살펴보겠습니다.

WebMvcConfigure가 제공하는 addInterceptor 함수를 사용해서 인터셉터를 등록할 수 있습니다.

@Configuration
  public class WebConfig implements WebMvcConfigurer {
  
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
          registry.addInterceptor(new ourInterceptor())
                  .order(1)
                  .addPathPatterns("/**")
		  .excludePathPatterns("/css/**", "/*.ico", "/error");
     }
    
}
  • registry.addInterceptor(new ourInterceptor()) : 인터셉터를 등록합니다.
  • order(1) : 필터에서와 마찬가지로 인터셉터의 호출 순서를 지정합니다. 낮을 수록 먼저 호출된다.
  • addPathPatterns("/**") : 인터셉터를 적용할 URL 패턴을 지정합니다.
  • excludePathPatterns("/css/**", "/*.ico", "/error") : 인터셉터에서 제외할 패턴을 지정합니다

 

 

정리하자면 서블릿 필터와 스프링 인터셉터는 웹과 관련된 공통 관심사를 해결하기 위한 기술입니다. 필터와 비교해서 인터셉터가 개발자 입장에서 좀 더 편리하고 정밀하게 사용할 수 있습니다. 컨트롤러가 호출되는 시점에 따라서 함수로 처리할 수도 있고, 특정 패턴을 addPathPatterns나 excludePathPatterns에 작성하면 쉽게 처리할 수 있습니다. 그러므로 특별한 문제가 없다면 인터셉터를 사용하는 것이 좋을 것 같습니다 !

댓글