웹 애플리케이션을 개발하다보면 필수적으로 개발해야하는 요소들이 있다.
Filter
나 Interceptor
은 위 요소들에 대한 책임 및 구현의 역할을 한다.
이를통해 들어온 요청이 DispatcherServlet
에 전달되기 전에 헤더를 검사해 인증 토큰이 있는지 없는지, 올바른지 올바르지 않은지 등을 검사할 수 있다.
Filter
나Interceptor
가 없다면 어떨까?
우리는 클라이언트의 요청에 대해 Controller, Service에서 저 동작들을 처리할 수 밖에 없다.
위 요소들은 모든 API에 공통적으로 수행되어야 하는 것들인데 애플리케이션이 커지면 커질수록 코드의 양은 많아지고, 중복이 생기고 애플리케이션이 무거워질 수 밖에 없다.
이 문제를 해결하기 위해서는 관심사의 분리가 필요하다!
관심사의 분리 장점
- 유지보수가 쉬워진다.
- 핵심 비즈니스 로직 개발에 집중할 수 있다.
- 코드의 재사용이 가능하다.
이 글에서는 Filter에 대해서 일단 알아보자.
스프링 프레임워크는 들어온 요청이 DispatcherServlet
에 의해 컨트롤러에 매핑된다. Filter
는 이 요청이 DispatcherServlet
에 의해 다뤄지기 전, 후에 동작한다.
Filter
는 웹 애플리케이션(web.xml...)에 등록한다.
요청 스레드가 DispatcherServlet
에 도착하기 전, 즉 스프링 컨테이너 외부에 존재하여 스프링과 무관한 자원에 대해 동작한다.
Filter
에서는 사용자의 요청 정보에 대한 검증, 데이터 추가나 변조, 자원 처리 후 응답 정보에 대한 변경 등의 처리가 가능하다.
주로 전역으로 처리해야하는 인코딩 변환, XSS 방어 등 웹 보안 관련 기능, 인증 기능 등을 수행한다.
간단하게 필터를 사용해보자
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.IOException;
@Slf4j
public class firstFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("첫 필터가 생성됐어요! > <");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("==================필터 시작=====================");
chain.doFilter(request, response);
log.info("==================필터 종료=====================");
}
@Override
public void destroy() {
log.info("첫 필터가 사라집니다!");
}
}
init()
필터가 생성될 때 수행되는 메소드
doFilter()
Request, Response가 필터를 거칠 때 수행되는 메소드
destroy()
필터가 소멸될 때 수행되는 메소드
필터 클래스를 만들었으니 이제 이 필터를 Spring Bean으로 등록해야한다.
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public FilterRegistrationBean myfirstFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new firstFilter());
return registrationBean;
}
}
필터를 빈으로 등록하기 위해 스프링 설정에 FilterRegistrationBean
을 이용해 직접 만든 필터를 등록할 수 있다.
나는 위에서 만든 firstFilter를 FilterRegistrationBean
을 이용해 스프링 필터로 등록했다.
등록한 필터가 어떻게 동작하는지 보기 위해 임시로 컨트롤러를 만들었다.
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Log4j2
@RestController
public class TestController {
@GetMapping("/")
public String hello(){
log.info("Hello!!");
return "Hello!!!";
}
}
이제 서버를 실행해서 url 요청을 보내면
스프링이 시작하면서 myFilter의 init()
메소드가 수행됐고 요청이 들어왔을 때, filter > Controller > filter 순서로 결과가 나온 것을 확인할 수 있다.
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import java.io.IOException;
@Slf4j
public class secondFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("두번째 필터가 생성됐어요!!!!");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.info("==================두번째 필터 시작=====================");
chain.doFilter(request, response);
log.info("==================두번째 필터 종료=====================");
}
@Override
public void destroy() {
log.info("내 두번째 필터가 사라집니다!");
}
}
@Bean
public FilterRegistrationBean myfirstFilter(){
....
}
@Bean
public FilterRegistrationBean mySecondFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new secondFilter());
return registrationBean;
}
secondFilter도 스프링 빈으로 등록해주고 실행하면
필터가 정상적으로 수행되는 것을 볼 수 있다.
순서는 FilterRegistrationBean의 setOrder() 메소드를 통해 결정할 수 있다.
Filter의 동작 순서를 더 자세히 살펴보자
출처
https://twofootdog.github.io/Spring-%ED%95%84%ED%84%B0%28Filter%29,-%EC%9D%B8%ED%84%B0%EC%85%89%ED%84%B0%28Interceptor%29,-AOP-%EC%B0%A8%EC%9D%B4%EC%A0%90/
https://velog.io/@_koiil/Filter-Interceptor-AOP
https://gardeny.tistory.com/35
https://goddaehee.tistory.com/154