서블릿 필터와 스프링 인터셉터

최지환·2023년 2월 13일
0

스프링

목록 보기
11/12
post-thumbnail

공통 관심 사항(cross-cutting concern)

애플리케이션 여러 로직에서 공통으로 관심이 있는 것

서블릿 필터, 스프링 인터렙터, 스프링 AOP 등으로 이런 공통 관심 사항을 처리 할 수 있다.

오늘은 서블릿 필터와 스트핑 인터셉터에 대해 알아보자.

필터

디스패처 서블릿 (Dispatcher Servlet)에 요청이 전달 되기 전/후에 부가작업을 처리할 수 있는 기능을 제공한다.

필터는 스프링 컨테이너가 아닌 톰캣 같은 웹 서블릿 컨테이너에 의해서 관리가 된다. 즉 필터는 스프링의 범위 밖에서 처리가 된다.

클라이언트로 부터 요청이 들어오면 웹 서블릿 컨테이너에서 필터를 거쳐, 스프링의 디스패처 서블릿거치는 것이다.

응답은 반대로 디스패쳐 서블릿을 거쳐 필터, 클라이언트 순으로 행한다.

필터의 위치 구상도

정리하자면 서블릿 필터가 있는 경우 요청은 다음과 같은 순서의 흐름을 가진다.

💡 HTTP 요청 → WAS → 필터(Servlet Filter) → 서블릿(Dispatcher Servlet) → 컨트롤러

그렇다면 필터를 한번 사용해보자.

우선 필터의 메서드를 알아보자. 필터는 javax.servlet 의 Filter 인터페이스를 구현해야한다.

이 인터페이스는 3가지의 메소드를 갖고 있다.

  • init()
  • doFilter()
  • destroy()

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

url-pattern에 맞는 모든 HTTP 요청이 Dispatcher Serlvet에 전달되기 전에 웹 컨테이너에 의해 실행 되는 메소드이다. doFilter()에 필요로 하는 처리 과정을 넣어 로직을 실질적으로 실행할 수 있도록 한다.
doFilter의 파라미터에는 FilterChain이 있는데 이는 뒤에서 설명하겠다.

destroy()

destroy 메소드는 필터 객체를 서비스에서 제거하고 사용하는 자원을 반환한다.


사용 예시

다음 코드는 모든 url요청에 대해서 uuid를 생성해 해당하는 요청에 대해 init, doFilter, destroy 실행 시 로그를 남기는 코드이다.

@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 {
        log.info("log filter doFilter");

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        String uuid = UUID.randomUUID().toString();

        try {
            log.info("REQUEST [{}][{}]", uuid, request);
            chain.doFilter(request, response);
        } catch (Exception e) {
            throw e;
        } finally {
            log.info("RESPONSE [{}][{}]", uuid, requestURI);
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy");
    }
}

필터 객체는 다음과 같이 스프링 빈 컨테이너에 등록을 해줘야한다.

		@Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new LogFilter()); //생성한 필터 객체 등록
        filterFilterRegistrationBean.setOrder(1); // 실행 순서 등록
        filterFilterRegistrationBean.addUrlPatterns("/*"); //요청 받는 url 형식 등록
        return filterFilterRegistrationBean;

결과

http://localhost:8080/items 라는 요청을 보냈을 때 실행 된 로그이다.

필터를 통해서 로그가 실행이 되었고 요청을 받고 이 요청이 필터로 들어갔을 때 REQUEST 로그가 찍히고, 응답이 필터로 들어 갔을 때의RESPONSE 로그가 찍힌 것을 확인 할 수 있다.

필터 체인

필터는 체인 방식으로 구성이된다.

필터를 하나만 쓸 수 있는 것이 아니라, 여러개 만들어 체인 형식으로 사용할 수 있다.

즉 필터1, 필터2, 필터3이 있는 경우 다음과 같은 요청 흐름을 가진다.

💡 HTTP 요청 → WAS → 필터1 →필터2 → 필터3→ 서블릿(Dispatcher Servlet) → 컨트롤러

응답은 이의 역순인

💡 컨트롤러 → 서블릿(Dispatcher Servlet) → 필터3 → 필터2 → 필터 1 → WAS → HTTP 요청

의 흐름으로 처리가 된다.


스프링 인터셉터

스프링 인터셉터는 서블릿 필터와 같이 웹과 관련된 공통 관심 사항을 효과적으로 해결 할 수 있도록 해주는 기술이다.

스프링 MVC가 제공하는 기술이다.

서블릿 필터와 유사하지만, 적용되는 순서와 범위, 사용방법은 다르다.

스프링 인터셉터의 특징

  • 디스패처 서블릿과 컨트롤러 사이에서 컨트롤러 호출 직전에 호출된다.
  • URL 패턴을 적용하여 요청을 처리할 수 있는데, 매우 정밀하게 설정이 가능하다.
  • 특정 요청을 처리하던 중 적절하지 않은 요청이라 판단하면 그 즉시 해당 요청을 끝낼 수 있다.
    스프링 인터셉터 제한

스프링 인터셉터의 위치 구상도

스프링 인터셉터의 흐름

스프링 인터셉터가 있는 경우 HTTP 요청은 다음과 같은 흐름으로 처리된다.

💡 HTTP 요청 → WAS → 필터→ 서블릿(Dispatcher Servlet) → 스프링 인터셉터 → 컨트롤러

스프링 인터셉터 제한

💡 HTTP 요청 → WAS → 필터→ 서블릿(Dispatcher Servlet) → 스프링 인터셉터 → 컨트롤러 // 정상 요청
HTTP 요청 → WAS → 필터→ 서블릿(Dispatcher Servlet) → 스프링 인터셉터 (비정상 요청이라고 판단, 컨트롤러 호출 X) // 비정상 요청

스프링 인터셉터 체인

스프링 인터셉터도 서블릿 필터 처럼 체인 방식을 지원한다.

💡 HTTP 요청 → WAS → 필터→ 서블릿(Dispatcher Servlet) → 인터셉터1 → 인터셉터2 → 컨트롤러


스프링 인터셉터의 메서드

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

preHandle()

컨트롤러 호출 전 실행되는 메서드이다. 따라서 컨트롤러 호출 전, 전처리 작업이나 요청 정보 가공을 할 때 사용 할 수 있다. 세번째 파라미터인 Object handler 는 호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.

💡
Object handler에는 다음과 같은 핸들러가 올 수 있다.
@RequestMapping : HandlerMethod
정적 리소스 : ResourceHttpRequestHandler

postHandle ()

PostHandle 메소드 컨트롤러를 호출 한 뒤에 실행된다. 파라미터로 컨트롤러가 반환하는 ModelAndView 에 대한 정보를 받을 수 있음

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

    @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();
        Object logId = (String) request.getAttribute("LOG_ID");
        log.info("RESPONSE [{}][{}][{}]", logId, requestURI, handler);

        if (ex != null) {
            log.error("afterCompletion error!!", ex);
        }
    }
}

스프링 인터셉터는 다음과 같이 등록한다. 이때 설정 파일에서는 WebMvcConfigurer를 구현해야한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new LoginMemberArgumentResolver());
    }

@Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1) // 순서
                .addPathPatterns("/**") // url 설정 
                .excludePathPatterns("/css/**", "/*.ico", "/error");//제외하는 경로 설정
    }
}

http://localhost:8080/items 호출시 다음과 같은 로그를 확인 할 수 있다.


스프링 인터셉터 호출 흐름

출처 : https://www.inflearn.com/course/스프링-mvc-2

0개의 댓글