둘 다 요청을 가로채는 장치지만 동작하는 계층이 다르다.
위치
클라이언트
↓
[서블릿 컨테이너 (톰캣)]
↓
★ Filter ← 서블릿 스펙. 스프링 밖
↓
[DispatcherServlet]
↓
★ Interceptor ← 스프링 MVC. 컨텍스트 안
↓
[HandlerAdapter]
↓
★ AOP (@Around) ← 프록시. 메서드 호출 단위
↓
[Controller]
바깥에서 안쪽 순으로 Filter → Interceptor → AOP다.
비교
| Filter | Interceptor | |
|---|---|---|
| 스펙 | 서블릿(J2EE) | 스프링 MVC |
| 관리 주체 | 서블릿 컨테이너 | 스프링 컨텍스트 |
| 스프링 빈 사용 | 가능하지만 설정 필요 | 자연스럽게 가능 |
| 요청/응답 교체 | 가능(chain.doFilter(new Wrapper(req), res)) | 불가 |
| 핸들러 정보 | 모른다 | 안다(HandlerMethod → 어떤 컨트롤러인지) |
| 예외 처리 | @ControllerAdvice가 못 잡는다 | 잡을 수 있다 |
| 적용 범위 | 모든 요청(정적 리소스 포함) | 스프링이 처리하는 요청 |
Filter를 쓰는 경우
@Component
public class LoggingFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
var wrapped = new ContentCachingRequestWrapper((HttpServletRequest) req);
chain.doFilter(wrapped, res); // 요청 객체를 교체해서 전달
}
}
| 용도 | 이유 |
|---|---|
| 인코딩 설정 | 스프링이 읽기 전에 해야 한다 |
| 요청/응답 본문 로깅 | 스트림은 한 번만 읽을 수 있어 래핑이 필요 |
| XSS 필터링 | 파라미터를 감싸서 치환 |
| 스프링 시큐리티 | 실제로 필터 체인으로 구현되어 있다 |
| CORS, 압축 | 전역적·저수준 처리 |
요청 본문 로깅은 Filter로만 제대로 할 수 있다 — 인터셉터 시점에는 이미 컨트롤러가 스트림을 읽을 예정이라, 여기서 읽으면 컨트롤러가 못 읽는다.
Interceptor를 쓰는 경우
public class AuthInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest req, HttpServletResponse res,
Object handler) {
if (handler instanceof HandlerMethod hm) {
var annotation = hm.getMethodAnnotation(LoginRequired.class);
if (annotation != null && !isLoggedIn(req)) {
res.setStatus(401);
return false; // false 면 컨트롤러로 안 간다
}
}
return true;
}
}
| 용도 | 이유 |
|---|---|
| 인증·인가 확인 | 어떤 컨트롤러인지 알아 어노테이션 기반 판단 가능 |
| 로그인 사용자 정보 세팅 | 스프링 빈을 자유롭게 쓸 수 있다 |
| API 호출 시간 측정 | 핸들러 단위 |
| 공통 모델 데이터 추가 | postHandle에서 ModelAndView 조작 |
세 메서드
preHandle() — 컨트롤러 실행 전. false 반환 시 중단
postHandle() — 컨트롤러 실행 후, 뷰 렌더링 전 (@RestController 에서는 의미 적음)
afterCompletion() — 뷰까지 끝난 후. 예외가 있어도 항상 실행 → 자원 정리에 적합
@ResponseBody를 쓰면 postHandle 시점에는 이미 응답이 나간 뒤라
응답을 바꿀 수 없다. REST API에서는 preHandle과 afterCompletion만 쓴다.
어떻게 고를까 — 판단 기준
- 요청/응답 객체를 바꿔야 하나? — → Filter
- 스프링이 처리하지 않는 요청도 포함해야? — → Filter
- 어떤 컨트롤러인지 알아야 하나? — → Interceptor
- 스프링 예외 처리에 태워야 하나? — → Interceptor
- 특정 메서드 호출만 감싸고 싶나? — → AOP
예외 처리 차이가 실무에서 중요하다 — Filter에서 던진 예외는
@ControllerAdvice가 못 잡아 톰캣의 기본 오류 페이지가 나간다.
일관된 JSON 오류 응답을 주려면 Filter 안에서 직접 응답을 써야 한다.