Servlet Filter & Spring Interceptor

dev_314·2023년 1월 29일
0

Spring - Trial and Error

목록 보기
2/7

모든 핸들러에서 로그인 여부 확인로직을 수행해야 한다.
그렇다고 로그인 여부 확인 코드를 모든 핸들러에 넣을 것인가?

Servlet의 FilterSpring Interceptor로 해결해보자

Servlet Filter

WAS에서 Servlet 객체를 호출하기 전, Filter를 호출할 수 있다.

Spring Web에서는 DispatcherServlet이 그림의 서블릿에 해당

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

용도에 맞게 Filter 인터페이스를 구현한 구현체를 Bean으로 등록하면 된다.

Spring Container에 Filter Bean 등록하기

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import javax.servlet.Filter;

@Configuration
public class Config {

	@Bean
    public FilterRegistrationBean myFilter() {
    	FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        // 사용할 필터를 설정
        filterRegistrationBean.setFilter(new MyFilter());
        // 필터 적용 순서를 지정
        filterRegistrationBean.setOrder(1);
        // 필터가 반응할 URL을 지정(모든 요청에 대해 반응)
        filterRegistrationBean.addUrlPatterns("/*");
       
        return filterRegistrationBean;
    }
}

참고

  • Servlet의 @WebFilter, Spring의 @ServletComponentScan을 통해 서블릿, 필터, 리스너를 등록할 수 있으나 필터 적용 순서를 지정하지 못한다는 단점이 있다.

Spring Interceptor

동일한 기능을 Spring Interceptor로 구현할 수 있다.

HTTP 요청 (클라이언트) -> WAS (Tomcat) -> 필터 (서블릿) -> 서블릿 (DispatcherServlet) -> 'Interceptor' -> Controller

인터셉터도 필터와 마찬가지로 Chaining이 가능하다

Spring Interceptor 구조

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;


public interface HandlerInterceptor {

	// 컨트롤러 호출 전 (정확히는 '핸들러 어댑터' 호출 전)
    // 반환값이 true -> 다음 인터셉터를 호출
    // 반환값이 false -> 진행 X (핸들러 어댑터, 컨트롤러 호출 X)
	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 {
	}
    
    // 응답 완료 후 (View가 Rendering 된 이후)
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
	}
}

서블릿 필터와 마찬가지로, 용도에 맞게 HandlerInterceptor 인터페이스를 구현한 구현체를 Bean으로 등록하면 된다.

참고

preHandle에서 request.setAttribute를 통해 postHandle, afterCompletion으로 데이터를 넘길 수 있다.
(인터셉터는 싱글톤처럼 사용되고, 멀티 스레드 환경을 측면에서 멤버 변수를 사용하면 위험하다.)

Spring Interceptor의 장점

1. 다양한 호출 시점

서블릿 필터는 단순히 요청이 발생하고 컨트롤러를 호출하기 직전에 doFilter를 한 번만 호출 할 수 있지만,
스프링 인터셉터는 컨트롤러 호출 전 (preHandle), 컨트롤러 호출 후 (postHandle), 응답 완료 후 (afterCompletion) 중 원하는 순간에 인터셉터를 호출할 수 있도록 했다.

2. 다양한 파라미터

서블릿 필터는 ServletRequest, ServletResponse만 다루는 반면, 스프링 인터셉터는 Handler, ModelAndView, Exception을 다룰 수 있다.

`Handler`
	호출할 Controller의 메서드
    요청에 따라 Handler 종류가 달라진다 
    (복습 필요,,,)

3. 따로 신경쓸 필요 없음(?)

서블릿 필터는 chain.doFilter()를 명시적으로 호출해야하는 반면, Interceptor는 그럴 필요 없다는 장점이 있다.

Interceptor와 예외

  1. Controller에서 예외가 발생하는 경우 PostHandle은 호출되지 않는다.

    DispatcherServlet에서 예외를 전달

  2. Controller에서 예외가 발생하더라도 afterCompletion은 항상 호출된다.

    예외가 발생하지 않으면 파라미터로 넘어온 Exception == null
    즉, 모든 상황에서 작동하길 바라는 종료 작업은 PostHandle이 아닌 afterCompletion에서 수행해야 한다.

Spring Container에 Interceptor 등록하기

HandlerInterceptor 구현체를 등록하는 방법은 약간은 다르다.

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

@Configuration
public class Config implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
        		.order(1)
                .addPathPatterns("/**") // 서블릿의 url 패턴과 약간 다르다 (모든 경로에 대해 적용)
                .excludePatterns("/css/**", "/*.ico", "/error"); // (특정 경로는 제외)
    }
}

참고: spring pathPattern

다음을 참고하자

ArgumentResolver 추가하기

공부 후 추후 정리,,,

profile
블로그 이전했습니다 https://dev314.tistory.com/

0개의 댓글