개발 업무를 하다보면 갖가지 인증 처리, 예외 처리등을 위해 Filter나 Interceptor를 사용해야 하는 부분이 많다. 특히 필자의 경우에는 어뷰징 방지등을 위한 코드로 Interceptor를 많이 사용하였다. 회사 코드 중에서는 어뷰징 방지 코드가 Service Layer에서 이루어지는 경우가 많은데, 비교하는 코드에서 중복코드가 몇 군데 있었고, 다른 코드에서 중복코드를 양산 할 수 있다는 생각이 들어 이번 기회에 Interceptor로 리팩토링을 하였다. Filter의 경우는 거의 사용해 본 적이 없는데, 자주 쓰는 Interceptor와 비교하여 그 내용을 이번 기회에 정리하고자 한다.
Spring Request Flow
이 이미지가 Spring MVC 구조를 가장 잘 보여주는 도식도 인 것 같아 첨부하였다. (많은 블로그에서 이 이미지를 사용하고 있는점은 안 비밀이다.)
그림에서 보면 가장 먼저 눈에 띄는 것은 Filter와 Interceptor의 실행 위치이다. Filter는 Dispatcher Servlet 이전에 실행된다. 정확히는 WAS내의 ApplicationContext에서 등록된 필터가 실행 된다. Filter는 J2EE 표준스펙이며, Servlet 2.3에 등장하였다. 따라서 Spring Framework가 아니어도 Servlet Filter를 사용할 수 있다.
Interceptor의 실행 위치는 DispatchetServlet 내부에서 실행이 되고 있다. 따라서 Interceptor는 Spring Framework에서 제공하는 API이며, 전후처리에 대한 편리한 인터페이스를 제공하고 있다. 공통적으로 Spring에서는 전후처리기로 많이 사용하고 있다. AOP와 함께 핵심 로직에 영향을 주지 않고 요청을 가로채어 처리하는 Spring의 큰 특징 중 하나로 생각된다.
Filter
위에서 잠깐 설명 했듯이, (Servlet) Filter는 J2EE 표준 스펙으로 Servlet API 2.3 부터 등장 하였다. 실행 위치는 WAS(Web Application Server) 내의 Application Context에 등록 된 필터가 요청 URL Pattern에 따라 실행 되도록 되어있다. 대표적인 예로는 Spring Security, CORS Filter등이 있다.
Filter Chain
Filter의 큰 특징으로는 Filter chain이라는 개념이 있다. 실제 Filter는 web.xml에 등록 된 Filter들을 WAS 구동 시에, WAS내의 ApplicationContext내의 Standard Context에 FilterMap이라는 Array에 등록된다. 실행 시에 요청 URL의 형식에 맞는 Pattern을 가진 Filter들로 Filter chain을 구성하게 되어 순차적으로 실행 되게 된다.
publicclassTestFilterimplementsFilter { @Override publicvoidinit(FilterConfig filterConfig)throws ServletException { //filter 생성 시 처리 }
@Override publicvoiddoFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException { //다음 Filter 실행 전 처리 (preHandle) //다음 filter-chain에 대한 실행 (filter-chain의 마지막에는 Dispatcher servlet실행) filterChain.doFilter(servletRequest, servletResponse); //다음 Filter 실행 후 처리 (postHandle) }
@Override publicvoiddestroy() { //filter 제거 시 처리 (보통 자원의 해제처리를 한다.) } }
설정 방법
Filter를 등록하는 방식을 크게 4가지 정도 있다.
web.xml 등록 방식
Java config 등록 방식 -> FilterRegistration Bean을 정의하여 추가할 Filter를 정의
java config 등록 방식 -> AbstractAnnotationConfigDispatcherServletInitializer에서 getServletFilter에 추가
<filter-mapping> <filter-name>testFilter</filter-name> <url-pattern>/*</url-pattern> <!-- url-pattern 대신 Servlet을 지정할 수도 있다. --> <servlet-name>testServlet</servlet-name> </filter-mapping> <filter-mapping> <filter-name>testFilter2</filter-name> <url-pattern>/*</url-pattern> <!-- url-pattern 대신 Servlet을 지정할 수도 있다. --> <servlet-name>testServlet</servlet-name> </filter-mapping>
다른 설정 파일 없이 Filter Class파일 하나만 가지고 Filter에 대한 설정을 할 수 있다. @Component -> Component-scan 시 Spring Bean으로 등록 된다. @WebFIlter -> Filter등록에 필요한 Interface를 제공한다. @Order -> @Component 어노테이션 사용 시 Order Interface 사용이 가능하다. Filter chain에 대한 순서를 지정 할 수 있다. 개인적으로 가장 깔끔한 방법이라 생각하지만, Ordering 시 헷갈릴 수 있을 것 같다는 생각이 들었다.
Interceptor
인터셉터는 주로 세션에 대한 체크, 인증 처리 등에 사용한다. 위에서 말했듯이 필자는 대부분의 어뷰징에 대한 처리를 인터셉터 단에서 처리하고 했다. 또한 특정 상품 진입 시, 권한 체크, 제약 조건 체크들도 인터셉터 단에서 처리 한 경험이 있다.
인터셉터도 필터와 비슷한 방식으로 작동한다. 하지만 필터와는 다르게 preHandle(), postHandle() 메소드가 구분 되어져 있어, 분기가 명확하다. 또한 Filter와는 다르게 handlerMethod를 파라미터로 제공하여 AOP 비스무리한 효과를 낼 수 있다. handler에서는 실행하고자 하는 컨트롤러에 대한 method 시그니처를 제공하여 좀 더 기능을 확장할 수 있다는 장점이 있다.