해당 글은 스프링 MVC2편
강의를 기반으로 작성합니다.
필터와 인터셉터의 차이를 명확하게 이해하고 사용하는 것은 중요하다. 추가적으로 AOP와 차이는 무엇일까?
라는 의문에서 다시 한번 강의를 들으면서 이해하고 마지막엔 나의 생각으로 각 차이를 정리해보기 위해 강의를 다시 보면서 공부해보자.
나의 웹 사이트를 모든 회원이 로그인 한 사용자만 사용하도록 하고 싶다.
이러한 것을 공통 관심사항이라고 할 수 있다.
모든 컨트롤러에 관련된 서비스 또는 컨트롤러에 로그인 인증 코드를 작성한다면 너무 많은
중복 코드가 작성되야한다.
근데 이런 공통 관심사를 AOP 를 이용해서 해결할 수 있지 않나?
일단 그래도 되겠지만 웹과 관련된 공통 관심사의 경우 서블릿 필터 또는 인터셉터를 사용한다.
웹과 관련된 공통 관심사를 처리할 때는 HTTP의 헤더나 URL의 정보들이 필요한데,
서블릿 필터나 스프링 인터셉터는 HTTPServletRequest
등과 같은 기능을 제공한다.
(웹과 관련된 부가기능을 많이 제공해준다.)
필터는 서블릿이 지원하는 문지기와 같다.
필터를 적용하면 필터가 호출 된 다음에 서블릿이 호출된다.
모든 고객의 요청 로그를 남기는 요구사항이 있다면 필터를 사용하면 된다.
필터는 특정 URL 패턴에 적용할 수 있다. /*
로 지정하는 경우 모든 요청에 필터가 적용된다.
스프링을 사용하는 경우 서블릿은 스프링의 디스패처 서블릿이라고 생각하자.
필터에서 적절하지 않은 요청이라고 판단한 경우 서블릿과 컨트롤러까지 요청을 전달하지 않는다.
필터를 체인형태로 엮어서 여러개의 필터를 사용할 수 있다.
로그 필터 ,로그인 필터 등등 (순서까지 정할 수 있음)
HTTP 요청 → WAS → 필터 1 → 필터 2 → 필터 N → 서블릿 → 컨트롤러
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()
: 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다. 필터의 로직을 구현 해두었다면 해당 로직이 실행된다.destory()
: 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.LogFilter 클래스 생성
구현체로 Filter
LogFilter implements Filter()
Filter()
를 구현하면 init()
doFilter()
destory()
메소드를 오버라이드 해서 사용가능하다.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("log filter doFilter");
}
doFilter의 인자로 있는 ServletRequest
의 경우 HttpServletRequest
의 부모 클래스이다. 따라서 부모 클래스에는 기능이 많이 없기 때문에 HTTP 요청을 처리하기에 적합한 HttpServletRequest
필터를 사용하기 위해서는 필터로직을 생성하고 필터를 등록하는 과정이 필요하다.
package com.example.springmvc.basic.reqeust.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
@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, 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");
}
}
package com.example.springmvc.basic.reqeust.filter;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
WebConfig 클래스를 만들고 @Bean
어노테이션으로 등록하고FilterRegistrationBean
을 등록해서 사용해준다.FilterRegistrationBean
: Spring에서 제공하는 클래스로 필터를 등록하는데 사용한다.setFilter()
: 등록할 필터를 지정한다.setOrder()
: 필터의 우선순위를 지정한다.addUrlPatterns()
: 지정된 URL 에 접근 시 필터를 실행시킨다.필터를 만들고 WebConfig 클래스에서 해당 필터들을 등록해서 사용할 수 있다.
위처럼 생성하고 등록해서 사용한다면 스프링부트 애플리케이션을 실행한 경우
init()
만 실행된다.
애플리케이션 실행 시 딱 한번만 실행되기 때문에 필터의 초기화 작업을 수행한다.
HTTP requset가 발생한 경우
doFilter()
에서 작성한 로직이 실행된다.
필터 내부로직으로 만들어둔 try문의 requset 로그가 출력되는 것을 확인 가능했다.
이후에 Controller
로직이 실행된 이후 response가 발생하면
finally에 작성해두었던 uuid가 출력된다.
주로 인증,로깅 요청 변환등을 처리한다.
스프링부트 애플리케이션이 종료되는 경우
destroy()
내부에 작성한 "log filter destroy" 문구가 출력된다.
리소스를 해제하거나 정리 작업을 수행한다.
예를 들어 열린 연결을 닫거나, 사용한 메모리를 정리할 수 있다.
정리하자면
init()
dofilter()
destroy()
아직 init()
과 destroy()
는 어떻게 활용,응용해서 사용할 지 감이 안잡힌다.
Chat GPT는 아래와 같은 상황에 사용하는 것을 추천했다.
init() 활용: 필터 초기화 시 필요한 설정을 로드하는 데 사용할 수 있습니다. 예를 들어, 데이터베이스 연결 정보나 외부 API의 키 등을 초기화할 수 있습니다.
destroy() 활용: 필터가 사용한 리소스를 정리하는 데 사용합니다. 예를 들어, 데이터베이스 연결을 닫거나, 로그 파일을 마무리하는 작업을 할 수 있습니다.
실무에서 HTTP 요청 시 같은 요청의 로그에 모두 같은 식별자를 자동으로 남기기 위해서는 logback mdc를 검색해서 사용해보자