filter vs interceptor vs AOP
- 개발
- 2026. 1. 13. 22:25
스프링으로 개발하다 보면, "요청이 들어왔을 때 공통적으로 처리해야 하는 로직"을 어디에 둘지 고민하게 됩니다. 예를 들어 인증, 로깅, MDC 세팅, 트래픽 제어, 요청 검증 같은 것들입니다. 스프링에는 이런 공통 처리를 위한 장치로 Filter, Interceptor, AOP라는 세 가지 선택지가 존재합니다.
문제는, 이 셋이 할 수 있는 일의 범위가 상당히 겹친다는 점입니다. 실제로 많은 경우, 필터로 구현한 로직을 인터셉터로 옮겨도 되고, 심지어 AOP로 구현해도 기능적으로는 큰 문제가 없어 보이기도 합니다. 그래서 "아무거나 써도 되는 거 아닌가?"라는 생각이 들기 쉽습니다.
하지만 이 세 가지는 동작 시점과 책임 레벨이 완전히 다릅니다. 이 차이를 무시한 채 사용하면, 당장은 동작하더라도 시스템이 커질수록 구조는 점점 흐려지고, 성능과 유지보수성 모두에서 비용을 치르게 됩니다.
이 글에서는 Filter, Interceptor, AOP가 각각 요청 처리 파이프라인의 어디에 위치하는지, 무슨 책임을 가져야 하는지, 그리고 어떤 상황에서 무엇을 선택하는 것이 아키텍처적으로 올바른지를 정리해보겠습니다. "된다/안 된다"의 문제가 아니라, "어디에 두는 게 맞는가"의 문제를 다루는 글입니다.
이 주제를 제대로 이해하기 위해서는, 먼저 Spring MVC의 요청 처리 구조부터 짚고 넘어갈 필요가 있습니다. Filter, Interceptor, AOP는 모두 이 흐름 위의 서로 다른 지점에서 동작하기 때문에, MVC 구조를 모르면 왜 이들을 구분해야 하는지도 명확해지지 않습니다.
스프링 MVC의 동작 방식

이 글은 Filter, Interceptor, AOP를 기능 나열 수준에서 비교하는 글이 아니라,
어떤 상황에서 무엇을 선택해야 하는지가 왜 중요한지를 구조적으로 설명하기 위한 글입니다.
따라서 Spring MVC 자체에 대한 세부적인 설명은 생략하고, 이 글의 주제와 직접적으로 연결되는 부분만 짚고 넘어가겠습니다.
우리가 여기서 주목해야 할 지점은 "서블릿 레벨"과 "컨트롤러 레벨" 크게 두 가지입니다.
Spring MVC 요청 흐름을 기준으로 보면,
Filter는 서블릿 컨테이너 레벨, 즉 Spring MVC에 진입하기 전 단계에서 동작하고
Interceptor는 컨트롤러 레벨, 즉 DispatcherServlet 내부에서 Controller 호출 전후를 감싸는 구조로 동작합니다.
즉, 이 둘은 같은 ‘요청’을 다루지만, 서로 완전히 다른 레이어에서 개입하는 도구입니다.
여기서 자연스럽게 이런 의문이 생깁니다.
"그럼 AOP는 이 흐름의 어디에 위치하는 걸까?"
결론부터 말하면, AOP는 Spring MVC 요청 흐름 자체와는 직접적인 관계가 없습니다.
AOP는 "요청"이 아니라, Spring Bean의 메서드 호출 시점을 기준으로 동작하는 메커니즘이기 때문입니다.
즉, AOP는 컨트롤러 요청 흐름을 제어하기 위한 도구가 아니라 비즈니스 로직 레벨의 공통 관심사를 분리하기 위한 도구이며,
Spring MVC가 아닌, Spring 전체 컨테이너 레벨의 기능이라고 보는 것이 정확합니다.
이 차이 때문에, Filter / Interceptor / AOP는 "할 수 있는 일"이 아니라, "책임져야 하는 레이어" 기준으로 구분해야 합니다.
이 부분은 뒤에서 각각의 역할을 설명하면서 더 자세히 다뤄보겠습니다.
Filter
그림을 다시 봐봅시다.

검정색이 cleint에서 나가는 방향이고 파란색이 client로 들어가는 방향입니다.
잘 보면, Filter는 서블릿 컨테이너와 DispatcherServlet 사이에서 동작합니다.
즉, Spring MVC에 요청이 진입하기 바로 직전 단계에서 요청과 응답을 가로채는 구조입니다.
반면, DispatcherServlet은 Interceptor를 통해 컨트롤러 실행 전후의 정보를 전달받으며 요청 흐름을 제어합니다.
Interceptor는 DispatcherServlet 내부에서, 특정 컨트롤러 호출을 기준으로 동작하는 구조이기 때문입니다.
이 둘의 위치 차이를 다른 말로 표현하면 다음과 같습니다.
Filter는 애플리케이션 전체 요청 흐름에 걸쳐 적용되는 전역 인프라 레벨의 개입 지점입니다.
그래서 Filter는 정적 리소스 요청이든, 존재하지 않는 URL이,든 어떤 컨트롤러로 매핑되든, 상관없이 서버로 들어오는 모든 요청 에 동일하게 적용됩니다. 이 의미에서, Filter는 서비스 전역에 퍼져 있는 인프라 계층의 장치라고 볼 수 있습니다.
Intercepter

이번에는 Interceptor의 동작 위치를 그림을 통해 먼저 확인해보겠습니다.
앞에서 살펴본 것처럼, Filter는 서블릿 컨테이너 레벨에서 서비스 전역에 적용되는 인프라 계층의 장치입니다. 즉, 어떤 요청이 들어오든 Spring MVC에 진입하기 전에 항상 동일하게 거쳐갑니다.
반면, Interceptor는 그렇지 않습니다.
Interceptor는 DispatcherServlet 내부에서, 특정 컨트롤러 호출을 기준으로 요청 흐름에 개입하는 구조를 가지고 있습니다. 다시 말해, Interceptor는 "모든 요청에 무조건 적용되는 전역 장치"가 아니라, Spring MVC 요청 처리 과정 안에서 선택적으로 동작하는 계층입니다.
이 차이는 곧, Filter와 Interceptor가 같은 문제를 풀 수 있어 보이더라도 서로 다른 레이어의 책임을 가진 도구라는 것을 의미합니다.
여기까지 살펴보면, Filter가 마치 모든 문제를 해결할 수 있는 만능 도구처럼 보일 수도 있습니다.
실제로 많은 기능을 Filter 하나로 처리할 수 있는 것도 사실입니다.
하지만 중요한 것은, "무엇을 할 수 있느냐"가 아니라 "어디까지의 흐름을 책임져야 하느냐" 입니다.
즉, 데이터 흐름의 범위(Scope) 에 따라 Filter를 선택할지, Interceptor를 선택할지를 판단해야 합니다.
그렇다면 어떤 기준으로 선택을 해야 할까요?
그 전에 알아야 할 용어가 있습니다. 바로 서블릿입니다. 일단 서블릿이 무엇인지 부터 알아야 설명이 가능할거 같습니다.
서블릿은 간단히 말해 HTTP 요청을 자바 애플리케이션 코드가 이해할 수 있게 만드는 어댑터 장치라고 생각하시면 됩니다.
그렇다는 이야기는 HTTP요청과 가장 밀접하게 전달이 되어지는 곳이라고 생각이 되어집니다.

대략적으로 그림으로 그리면 위와 같은 그림이 그려지죠. Filter은 서블릿과 자바 애플리케이션 사이에 있을겁니다.
그렇다면 HTTP요청으로는 의미 있는데 스프링 MVC까지 가지 않는것들을 처리할때 유용할거로 여겨집니다.
HttpServletRequest
HttpServletResponse
HTTP에 요청은 어떤것들을 받을까요?
HTTP 요청에는 크게 4가지 덩어리가 들어옵니다.
1. 요청 라인
- 무엇을, 어떤 방식으로, 어디에 요청하는지
- ex) GET /users/1 HTTP/1.1
2. 헤더
- 요청에 대한 부가 정보들
- 인증정보, 데이터 형식, 쿠키, 클라이언트 정보등등
3. 바디
- 서버에게 보내는 실제 데이터
4. 기타 메타 정보 (프로토콜, 연결 정보 등)
- 통신과 연결에 대한 정보
- 클라이언트 IP, 포트, HTTPS 정보, 세션 정보
응답도 마찬가지입니다.
1. 상태 라인
- 요청 처리 결과가 어땠는지
- HTTP/1.1 200 OK
2. 헤더
- 응답에 대한 부가 정보들
- 응답 데이터 타입, 길이, 쿠키, 캐시 정책
3. 바디
- 클라이언트에게 실제로 보내는 데이터
- JSON, HTML, 파일, 바이너리..
4. 기타 메타 정보
- 통신과 연결에 대한 정보
- 서버 정보, 연결 유지 정보, TLS 정보, 세션 정보
사실 인터셉터를 학습하기 이전이라 섯불이 판단하기에는 이릅니다. 그럼 인터셉터도 알아봅시다.
인터셉터는 서블릿이 아닌 스프링 MVC에서 동작이 되어지는 장치입니다. 어떻게 보면 스프링이라는 생태계에 살기위해 존재하는 그런 존재라고 여겨집니다.
그렇다면 스프링 MVC의 요청은 어떤것을 받고 응답할까요?
Spring MVC는 "서블릿이 만든 요청/응답 객체"를 받아서, 그걸 "컨트롤러 메서드 호출"로 바꿔주고, 결과를 다시 응답 객체에 써주는 프레임워크라고 하는군요.
그럼 여기서 의문점이 왜 구분을 지었을까요? HTTP요청을 바로 써도 괜찮지 않을까요?
그 이유는 HTTP요청과 비즈니스 요청을 분리하기 위해서입니다.
이로써 Spring MVC은 HTTP 파싱, Content-Type 분기, JSON 직렬화/역직렬화, 인코딩, 에러 매핑, 상태 코드 처리를 감췄습니다.
이로서 우리가 판단하는 기준은 HTTP 요청인지 비즈니스 요청인지에 따라 필터를 쓸지 인터셉터를 쓸지 판단할 수 있다고 생각합니다.
그렇다면 AOP는 어디에 속해야 할까요? 지금까지 필터랑 인터셉터만 얘기를 했었는데 AOP에 대해서는 한 마디도 하지 않았죠. 그 이유는 AOP는 위 정보들과 전혀 상관이 없기 때문입니다.
AOP는 자바 메서드와 관련이 있습니다.
AOP는 메서드 실행과 관련이 되어있는 것들이 기준입니다.
예를 들면, @Transactional, @Cacheable, 실행 시간 측정, 검사로그, 재시도, 권한 체크등입니다.
이들의 공통점은 HTTP가 없어도 실행이 가능하다는 공통점을 가지고 있습니다.
즉, AOP는 웹 기술이 아니라, 객체/메서드 실행 제어 기술입니다.
| 구분 | Filter | Interceptor | AOP |
| 기준 | HTTP 요청 | Controller 호출 | 메서드 호출 |
| 다루는 것 | Request/Response | + Handler | JoinPoint |
| 레벨 | 인프라 | MVC | 비즈니스 |
마지막으로 기본적으로 제공하는 Filter, Intercepter는 어떤것들이 있을까?
Filter
| 이름 | 역할 | 언제 쓰이나 |
| CharacterEncodingFilter | 요청/응답 인코딩 UTF-8 강제 | 한글 깨짐 방지 |
| HiddenHttpMethodFilter | HTML Form에서 PUT/DELETE 흉내 | _method=PUT 같은 거 |
| FormContentFilter | PUT/PATCH Body를 파라미터처럼 읽게 | 폼 기반 요청 |
| RequestContextFilter | Request를 ThreadLocal에 바인딩 | 어디서든 request 접근 |
| ForwardedHeaderFilter | 프록시 환경에서 X-Forwarded-* 처리 | 로드밸런서 뒤에 있을 때 |
| Spring Security Filter Chain | 인증/인가/세션/CSRF 등 | 보안 전부 |
| (Tomcat 기본) ErrorPageFilter 등 | 에러 처리 | 컨테이너 레벨 |
Intercepter
| 이름 | 역할 | 언제 쓰이나 |
| LocaleChangeInterceptor | ?lang=ko 같은 걸로 언어 변경 | 다국어 사이트 |
| ThemeChangeInterceptor | 테마 변경 (요즘 거의 안 씀) | UI 테마 |
| AsyncHandlerInterceptor | 비동기 요청 컨텍스트 처리 | async 요청 |
마무리
| 구분 | Filter | Interceptor |
| 레벨 | HTTP 인프라 | Spring MVC |
| 기본 제공 수 | 많음 | 적음 |
| 대표 역할 | 인코딩, 보안, 파싱, 컨텍스트 바인딩 | 로케일, MVC 흐름 제어 |
| 우리가 주로 만드는 것 | 로깅, MDC, RequestId, Wrapping | 인증 체크, 권한 체크, UserContext |
Filter을 사용해야하는지 Intercepter를 사용해야 할지는 HTTP 요청/응답을 사용하는지 비즈니스 요청/응답인지 생각해야 하는 문제라고 생각합니다. 만약에 HTTP를 이용해서 어떠한 액션을 하고 싶다면 Filter, 비즈니스 요청을 다룬다면 인터셉터를 이용하는게 좋습니다. 그리고 AOP는 HTTP나 MVC와는 무관하게, 자바 메서드 실행 단위로 동작하며, 트랜잭션이나 캐시 같은 비즈니스 로직의 횡단 관심사를 처리하는 데 사용됩니다.
'개발' 카테고리의 다른 글
| 새로운 인증 전략 PASETO (1) | 2026.01.11 |
|---|---|
| 캐시를 이용하는 방법들 (0) | 2026.01.09 |
| Lint는 무엇인가? (1) | 2026.01.08 |
| MDC란 무엇인가 그리고 어떻게 설정할 수 있을까? (1) | 2026.01.06 |
| WAL: 분산 시스템에서 쓰기 경로를 중앙화하는 방법 (0) | 2026.01.03 |