웹 애플리케이션을 개발하다 보면 여러 컨트롤러에 걸쳐 공통적으로 처리해야 할 작업들이 생깁니다. 예를 들어, 모든 요청에 대한 로깅, 특정 요청에 대한 인증/인가 처리, 요청/응답 데이터의 전처리 후처리 작업이 있습니다. 이런 로직을 필요한곳에 매번 작성하는 것은 중복과 유지보수 비용이 많이 발생하게 됩니다.

Servlet Filter를 사용하여 보다 간단히 처리할 수 있습니다. 스프링 부트 환경에서 이 필터를 어떻게 구현하고, 등록하는 것인지 정리하였습니다.

1. 기본 필터 만들어보기: 요청 시간 측정 로거

먼저 간단하면서도 실용적인 필터를 하나 만들어 보겠습니다. 모든 API 요청이 들어올 때 시작 시간을 기록하고, 요청 처리가 끝났을 때 종료 시간을 기록하여 총 소요 시간을 로그로 남기는 필터입니다.

jakarta.servlet.Filter 인터페이스를 상속받고 doFilter()메서드를 오버라이드해야 합니다.

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.UUID;

@Slf4j
public class RequestLoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();
        String requestId = UUID.randomUUID().toString();

        log.info("REQUEST [{}][{}]", requestId, requestURI);
        long startTime = System.currentTimeMillis();

        // 다음 필터 또는 서블릿으로 요청 전달
        chain.doFilter(request, response);

        long duration = System.currentTimeMillis() - startTime;
        log.info("RESPONSE [{}][{}][{}ms]", requestId, requestURI, duration);
    }
}

💡 잠깐! chain.doFilter(request, response) 코드가 바로 필터의 핵심입니다. 이 코드를 기준으로 전(pre-processing)에는 요청 처리 전 공통 기능을, 후(post-processing)에는 응답 처리 후 공통 기능을 수행할 수 있습니다.

2. 필터 등록하기: 두 가지 방법, 언제 무엇을 쓸까?

이제 위에서 만든 RequestLoggingFilter를 스프링 부트가 인식하도록 등록해야 합니다. 등록 방법은 크게 두 가지가 있으며, 각각의 장단점과 사용 사례가 명확합니다.

방법 1: @Component - 가장 간단하고 빠른 방법

@Component
@Slf4j
public class RequestLoggingFilter implements Filter {
    // ... (doFilter 로직은 위와 동일)
}

가장 쉬운 방법이며 필터 구현체에 @Component 어노테이션을 사용하여 스프링 빈으로 등록합니다.

  • 애플리케이션의 모든 요청(/*)에 대해 필터를 적용하고 싶을 때 사용합니다. 따라서 특정 URL 패턴에만 필터를 적용하거나 세밀한 순서 제어가 어렵습니다.
    • @Order 어노테이션을 사용하여 필터간의 순서를 간단하게 제어할 수 있습지만 특정 URL에만 필터 적용은 불가합니다.
  • 빠르게 프로토타이핑하거나 간단한 공통 로깅 필터를 추가할 때 매우 유용합니다.

방법 2: FilterRegistrationBean - 세부 설정

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<RequestLoggingFilter> loggingFilter() {
        FilterRegistrationBean<RequestLoggingFilter> registrationBean = new FilterRegistrationBean<>();

        registrationBean.setFilter(new RequestLoggingFilter()); // 등록할 필터 지정
        registrationBean.addUrlPatterns("/api/*"); // '/api/'로 시작하는 URL에만 적용
        registrationBean.setOrder(1); // 필터 체인에서 1번 순서로 실행
        registrationBean.setName("RequestLoggingFilter"); // 필터 이름 지정

        return registrationBean;
    }
}

적용할 URL 패턴이나 실행 순서를 명확하게 제어해야 하는 경우 FilterRegistrationBean을 사용합니다. @Configuration 클래스를 생성하고 FilterRegistrationBean을 반환하는 @Bean 메서드를 정의합니다.

  • 특정 경로(/api/*, /admin/* 등)에만 적용해야 할 때
  • 여러 필터가 존재하여 실행 순서를 명확하게 정의해야 할 때 (로깅 필터 -> 인증 필터)
  • 특정 환경(개발,운영)에 따라 필터를 활성화, 비활성화해야 할 때

3. 마무리

구분 @Component 방식 FilterRegistrationBean 방식
적용 범위 모든 URL (/*) 특정 URL 패턴 지정 가능
적용 목적 간단한 글로벌 필터 세밀한 제어가 필요한 상황
실행 순서 @Order로 제어 (제한적) setOrder로 명시적 제어 (권장)
편의성 매우 간편함 설정 코드 필요
  • 서블릿 레벨에서 동작. 스프링 컨텍스트와 무관하게 동작하며, 주로 보안(Spring Security), 인코딩, 로깅 등 저수준의 공통 기능에 사용됩니다.
  • “일단 @Component로 간단하게 시작하고 URL 패턴이나 순서 제어가 필요해지면 FilterRegistrationBean으로 전환을 고려하면 됩니다.