스프링에서는 애플리케이션에서 공통적인 처리를 위해 활용할 수 있도록 제공하는 것이 3가지가 있다.
- Filter
- Interceptor
- AOP
위 3가지의 공통적인 특징은 어떤 행동이나 처리를 하기 전에 전처리 작업을 하거나, 행동이나 처리를 한 후에 후처리 작업을 할 수 있도록 제공한다는 것이다.
Spring Life Cycle과 DelegatingFilterProxy
아래의 그림은 스프링의 라이프 사이클을 잘 설명해주고 있는 그림이다.
필터는 스프링 영역 밖에서 실행된다는 것을 주의하자 스프링 영역 밖이라는 뜻은 필터를 Bean으로 주입도 할 수 없고 Bean객체를 주입(DI) 받을 수도 없다. DelegatingFilterProxy가 등장하기 전에는...
스프링 세상에 DelegatingFilterProxy가 등장하면서 Filter가 Bean으로 등록이 되고, Bean객체를 주입도 받을 수 있다.
하지만 위의 내용은 예전의 스프링에서의 말이다. 현 시대의 스프링 부트를 사용한다면 스프링 부트가 DelegatingFilterProxy 없이도 Filter를 상속받는 Bean객체를 FilterChain에 알아서 등록해준다.
결론은, 위의 그림에서는 스프링영역 밖에 있지만 현시대에서는 스프링 부트가 서블릿 기술인 필터를 알아서 Bean으로 등록, 주입을 해준다. 무려 DelegatingFilterProxy가 없어도...
Custom Filter 생성
Custom Filter를 생성하는 방법은 간단하다. Filter를 implements 상속받아 init(), doFilter(), destroy()를 재정의(Override)해주면 된다.
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.IOException;
/**
* Class : MyFilter
* Author : Red Juns
* Description : Custom My Filter....
* History : [2022-06-08] - Red Juns - Class Create
*/
@Slf4j
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.debug("My Filter Init!!!");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.debug("My Filter do........");
log.debug("어떤 처리를 하는중....");
filterChain.doFilter(servletRequest, servletResponse);
log.debug("Response로 나가는 중....");
}
@Override
public void destroy() {
log.debug("My Filter Destroy....");
}
}
init() 함수는 필터가 생성되는 시점에 실행될 함수이다. doFilter는 위의 그림처럼 ServletReqeust, ServletResponse를 주고받고 FilterChain과 함께 실행된다.
doFilter메서드 내용은 해당 필터에서의 처리를 마무리 한 뒤에 filterChain에게 ServletRequest, ServletResponse를 전달하면서 다음 필터에게 전달한다.
destroy() 함수는 필터가 종료되면서 실행될 함수이다.
Custom Filter 체인 등록
Custom Filter를 체인에 등록하는 방법은 크게 3가지이다.
- FilterRegistrationBean을 이용한 방법
- @Component로 Bean으로 등록하여 스프링 부트가 체인에 등록하게 하는 방법
- @WebFilter로 @ServletConponentScan으로 Bean으로 등록하게 하는 방법
해당 포스트에서는 @Component와 @WebFilter로 등록하는 방법을 다루겠다.
@Component로 FilterChain 등록
@Component 어노테이션만 달아주면 스프링부트가 알아서 Filter를 상속받은 Bean객체를 필터 체인에 연결한다.
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.IOException;
import org.springframework.stereotype.Component;
/**
* Class : MyFilter
* Author : Red Juns
* Description : Custom My Filter....
* History : [2022-06-08] - Red Juns - Class Create
*/
@Slf4j
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.debug("My Filter Init!!!");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.debug("My Filter do........");
log.debug("어떤 처리를 하는중....");
filterChain.doFilter(servletRequest, servletResponse);
log.debug("Response로 나가는 중....");
}
@Override
public void destroy() {
log.debug("My Filter Destroy....");
}
}
내장 톰캣(WAS)으로 실행해보면 아래와 같이 로그가 찍힌다.
[DEBUG] [MyFilter.java(init) - 21 Line] - My Filter Init!!!
...
[INFO ] [TomcatWebServer.java(start) - 220 Line] - Tomcat started on port(s): 3333 (http) with context path ''
[INFO ] [StartupInfoLogger.java(logStarted) - 61 Line] - Started MutualMapApplication in 1.475 seconds (JVM running for 13.74)
.... API Call
[DEBUG] [MyFilter.java(doFilter) - 26 Line] - My Filter do........
[DEBUG] [MyFilter.java(doFilter) - 27 Line] - 어떤 처리를 하는중....
.... API 처리....
[DEBUG] [MyFilter.java(doFilter) - 30 Line] - Response로 나가는 중....
... 톰캣 종료....
대상 VM에서 연결 해제되었습니다. 주소: '127.0.0.1:8771', 전송: '소켓'
[DEBUG] [MyFilter.java(destroy) - 35 Line] - My Filter Destroy....
UrlPattern
@Component로 필터를 동록할때 주의할 점은 모든 UrlPattern에 대해서 필터가 적용된다는 것이다.
아래는 ServletContextInitializerBeans에서 필터를 등록한 결과를 로그 찍은 것이다.
[DEBUG] [ServletContextInitializerBeans.java(logMappings) - 236 Line] -Mapping filters:
springSecurityFilterChain urls=[/*] order=-100
, filterRegistrationBean urls=[/*] order=2147483647
, characterEncodingFilter urls=[/*] order=-2147483648
, formContentFilter urls=[/*] order=-9900
, requestContextFilter urls=[/*] order=-105
, log4j2MDCFilter urls=[/*] order=100
, httpServletFilter urls=[/*] order=101
, myFilter urls=[/*] order=2147483647
마지막 줄에 myFilter의 url매핑이 모든 Url에 매핑된 것을 확인할 수 있다. 추가로 order는 2147483647로 가장 마지막의 Chain순서를 가진 것을 알 수 있다.
@Order
필터의 순서를 지정하는 방법은 @Order 어노테이션을 사용하여 지정할 수 있다.
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import java.io.IOException;
@Slf4j
@Component
@Order(value = 200)
public class MyFilter implements Filter {
..... 생략
}
200이라는 값을 설정하고 난 뒤 필터 체인에 등록된 로그는 아래와 같다.
[DEBUG] [ServletContextInitializerBeans.java(logMappings) - 236 Line] -Mapping filters:
springSecurityFilterChain urls=[/*] order=-100
, filterRegistrationBean urls=[/*] order=2147483647
, characterEncodingFilter urls=[/*] order=-2147483648
, formContentFilter urls=[/*] order=-9900
, requestContextFilter urls=[/*] order=-105
, log4j2MDCFilter urls=[/*] order=100
, httpServletFilter urls=[/*] order=101
, myFilter urls=[/*] order=200
@ServletComponentScan으로 필터 체인 연결
main 클래스에 @ServletComponentScan 어노테이션으로 스캔할 상위 패키 지명을 설정하여 스캔할 수 있다.
@ServletComponentScan(basePackages = "com.map.mutual.side")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
MyFilter.Java
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/myFilter/*")
@Order(value = 200)
public class MyFilter implements Filter {
.......
}
필터가 등록되었는지 로그를 확인해보자.
[DEBUG] [ServletContextInitializerBeans.java(logMappings) - 236 Line] - Mapping filters:
springSecurityFilterChain urls=[/*] order=-100
, filterRegistrationBean urls=[/*] order=2147483647
, characterEncodingFilter urls=[/*] order=-2147483648
, formContentFilter urls=[/*] order=-9900
, requestContextFilter urls=[/*] order=-105
, log4j2MDCFilter urls=[/*] order=100
, httpServletFilter urls=[/*] order=101
, com.map.mutual.side.common.filter.MyFilter urls=[/myFilter/*] order=2147483647
마지막 줄에 com.map.mutual.side.common.filter.MyFilter urls=[/myFilter/*] order=2147483647와
같이 로그가 찍힌 것을 볼 수 있다.
???? urls는 설정한 대로 /myFilter/*로 설정되었지만 order가 설정되지 않았다.
@ServletComponentScan은 servletComponentRegisteringPostProcessor가 스캔한 순서대로 order를 지정하기 때문에 순서를 설정할 수 없다.
@Component와 @ServletComponentScan 이중 사용 시 주의.
필터에 @Component와 @WebFilter로 서블릿 컴포넌트로 스캔하게 되면 동일한 필터가 체인에 2개가 등록이 된다..
주의해야 한다.!!
'Java > SpringBoot' 카테고리의 다른 글
RESTful API 설계 및 구현 가이드 (0) | 2023.12.08 |
---|---|
[Spring Boot] Spring Cloud GateWay 필터 추가 (0) | 2022.06.14 |
[SpringBoot] JPA Entity Listener 엔티티 이벤트 리스너 (2) | 2022.04.26 |
[SpringBoot] Exception Handler 예외를 통합관리 하자.!!! (0) | 2022.04.15 |
[SpringBoot] Log4j2 기본 사용법 (0) | 2022.03.07 |