Servlet Filter와 Spring Interceptor

김용현·2024년 1월 11일
0

Spring

목록 보기
9/13

본 포스트는 김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의를 토대로 작성하였습니다.

Servlet Filter

서블릿 필터란 스프링이 아닌 자바 서블릿에서 제공하는 기능으로 클라이언트 요청이 들어오면 가장 최전방에서 응답을 필터링 하는 곳이다.

필터는 체인처럼 연결되어 있으며, 해당 필터를 통과하면 다음 필터를 호출하는 식으로 이루어져 있다.

개발자는 Filter 인터페이스를 상속받아 원하는 필터를 만들 수 있으며 이를 원하는 곳에 등록할 수 있다.

Filter 인터페이스

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의 인자로 들어오는 chain을 이용하여
chain.doFilter(request, response)를 호출하면 된다.

package hello.login.web.filter;
 import lombok.extern.slf4j.Slf4j;
 import javax.servlet.*;
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.util.UUID;
 @Slf4j
 public class LogFilter implements Filter {
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
         log.info("log filter init");
     }
@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() {
        log.info("log filter destroy");
    }
}

다음 예시처럼 Filter를 상속받아 구현하면 된다.

이렇게 구현하고 나면 필터를 등록해야 한다.
필터를 등록하는 건 스프링 부트를 이용하도록 하자.

//WebConfig 클래스
package hello.login;
 import hello.login.web.filter.LogFilter;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import javax.servlet.Filter;
 
 @Configuration
 public class WebConfig {
     @Bean
     public FilterRegistrationBean logFilter() {
         FilterRegistrationBean<Filter> filterRegistrationBean = new
 FilterRegistrationBean<>();
 		 filterRegistrationBean.setFilter(new LogFilter());
         filterRegistrationBean.setOrder(1);
         filterRegistrationBean.addUrlPatterns("/*");
         return filterRegistrationBean;
	} 
}

환경설정을 할 수 있도록 @Configuration을 붙여주고 해당 필터를 등록하면 된다.
setOrder는 필터 체인 순서이다. 숫자가 낮을수록 먼저 동작한다.
addUrlPatterns는 필터를 적용할 url 패턴을 지정한다. 한 번에 여러 패턴을 지정할 수도 있다.

스프링 인터셉터

서블릿 필터가 가장 전방에서 응답을 처리한다면 스프링 인터셉터는 Dispatcher Servlet에서 컨트롤러를 호출하는 과정에서 작동한다.

작동 흐름은

다음과 같은데, 중간에 preHandle, postHandle, afterCompletion 등의 메소드들이 실행된다.

  • preHandle
    핸들러 어댑터 호출 전에 호출된다.
  • postHandle
    핸들러 어댑터 호출 후에 호출된다.
    컨트롤러에서 예외 발생 시 postHandle은 호출되지 않는다.
  • afterCompletion
    뷰가 렌더링 된 이후에 호출된다.
    예외가 발생하더라도 항상 호출된다.
@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 {
        String requestURI = request.getRequestURI();
        String uuid = UUID.randomUUID().toString();
        request.setAttribute(LOG_ID, uuid);
		//@RequestMapping: HandlerMethod
        //정적 리소스: ResourceHttpRequestHandler 
        if (handler instanceof HandlerMethod) {
        	HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
        }
        log.info("REQUEST  [{}][{}][{}]", uuid, 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 {
        String requestURI = request.getRequestURI();
        String logId = (String)request.getAttribute(LOG_ID);
        log.info("RESPONSE [{}][{}]", logId, requestURI);
        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
	} 
}

각 함수마다 request, response 외에 인자로 하나씩 더 들어오는데, 실행 시점에 따라 갖고 있을 수 있는 정보들이다.

preHandle의 경우 handler 정보가 들어오는데 보통 @Controller, @RequestMapping 을 사용하면 HandlerMethod가 넘어온다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
				.excludePathPatterns("/css/**", "/*.ico", "/error");
		}
//...
}

이렇게 만든 인터셉터를 등록하려면 WebMvcConfigurer가 제공하는 addInterceptors를 활용하면 된다.

  • registry.addInterceptor(new LogInterceptor()): 인터셉터를 등록한다.
  • order(1) : 인터셉터의 호출 순서를 지정한다. 낮을 수록 먼저 호출된다.
  • addPathPatterns("/**") : 인터셉터를 적용할 URL 패턴을 지정한다.
  • excludePathPatterns("/css/*", "/.ico", "/error") : 인터셉터에서 제외할 패턴을 지정한다
profile
평생 여행 다니는게 꿈 💭 👊 😁 🏋️‍♀️ 🦦 🔥

0개의 댓글

관련 채용 정보