| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- 백엔드
- selector
- Java
- webflux
- docker
- SpringBoot
- prometheus
- Kubernetes
- DevOps
- spring boot
- mysql
- NIO
- monitoring
- 성능최적화
- 동시성제어
- grafana
- JPA
- 트랜잭션
- 백엔드개발
- helm
- redis
- kafka
- RDBMS
- CloudNative
- 데이터베이스
- 성능 최적화
- Kotlin
- GitOps
- jvm
- netty
- Today
- Total
유성
Filter와 Interceptor: 자원의 '재정의'와 '활용'에 관한 설계적 필연성 본문
필터(Filter)와 인터셉터(Interceptor)는 흔히 "공통 기능을 처리하는 도구" 또는 "비즈니스 로직과는 별개의 공통 관심사를 처리하는 도구" 정도로 이해되곤 한다.
이 글에서는 "어떤 계층에서 자원의 소유권을 정의할 것인가" 를 결정하는 전략적 요충지로서 필터와 인터셉터를 알아본다.
글을 시작하기에 앞서, 우리가 흔히 하는 오해 하나를 짚고 넘어가려 한다. (접은글 확인)
사실 현대적인 프레임워크 환경에서 필터(Filter)와 인터셉터(Interceptor)의 기능적 경계는 그리 절대적이지 않다.
인터셉터에서 할 수 있는 일의 상당 부분은 필터에서도 가능하기 때문이다.
필자 역시 신입 시절, 이 둘을 '기능적 차이'로만 구분하려다 혼란에 빠졌던 기억이 있다.
특히 Spring Security가 DelegatingFilterProxy를 통해 서블릿 필터 체인 위에서 스프링 빈의 모든 기능을 활용하는 것을 보며, '데이터 수정이 아닌 경우 어디서든 처리만 되면 그만이 아닌가?'라는 의구심을 갖기도 했다.
사실 기능 구현 측면에서는 틀린 말이 아니다.
하지만 자원의 생명주기를 정교하게 제어해야 하는 경우 이야기는 달라진다.
오늘 이 글에서는 '어디서든 할 수 있다'는 오해를 넘어, '왜 반드시 그곳이어야 하는가'에 대한 설계적 필연성을 다뤄보고자 한다.
1. Context의 깊이: Servlet Container vs Spring Context
필터와 인터셉터의 가장 근본적인 차이는 그들이 살아가는 '생태계'에 있다.
- Filter: 서블릿 컨테이너 레벨에서 동작한다. 스프링의 존재조차 모르는 요청이 가장 먼저 마주하는 관문이다. 따라서 보안(CORS, XSS 필터링)이나 전역적인 인코딩, 그리고 우리가 앞서 다룬 ContentCachingRequestWrapper를 통한 스트림 래핑처럼 '로우 레벨 자원'을 제어하기에 최적의 장소다.
- Interceptor: DispatcherServlet 내부에서 실행된다. 즉, 스프링 빈(Bean)에 접근이 용이하고 핸들러(Controller) 매핑 정보와 메서드 시그니처를 알고 있다. 비즈니스 로직과 밀접한 권한 체크, 데이터 가공 등에 적합하다.
요즘은 Spring Boot가 내장 톰캣을 품고 있어 Servlet과 Spring의 영역이 모호해 보이지만, 이 경계들은 분리해서 보아야 한다.
2. 자원 제어의 철학: '재정의'할 수 있는 자와 '활용'만 하는 자
서블릿 컨테이너가 설계한 계층별 권한의 차이를 이해할 필요가 있다.
- 필터의 권한(자원의 재정의): 필터는 FilterChain을 통해 ServletRequest 자체를 교체(Wrapping)할 수 있는 유일한 계층이다. 한 번 읽으면 소멸하는 InputStream을 가로채서, 메모리나 OS 버퍼에 복사본을 담은 새로운 요청 객체로 자원의 물리적 성질을 변형할 수 있는 '자원의 재정의 권한'을 가진다.
- 인터셉터의 권한(자원의 활용): 반면 인터셉터는 DispatcherServlet이 이미 결정해버린 요청 객체를 인자로 전달받을 뿐이다. 인터셉터 내부에서 요청 객체 자체를 다른 객체로 갈아끼우는 것은 불가능하다. 오직 전달받은 자원을 어떻게 활용할지(Business Logic) 결정하는 '실행의 권한'만 가진다.
3. 실무적 설계 전략: 무엇을 어디에 둘 것인가?
복잡도를 낮추고 시스템의 결합도를 느슨하게 유지하기 위해 다음과 같은 기준을 세워야 한다.
Filter에 두어야 할 것 (Low-level Concerns)
- Logging & Tracing: 요청의 시작과 끝을 기록하고 MDC에 Trace ID를 심는 작업
- Security: 인증되지 않은 요청이 스프링 컨텍스트 깊숙이 들어와 자원을 낭비하지 않도록 막는 작업
- Caching Wrapper: 후속 레이어에서 바디를 재사용할 수 있도록 DirectByteBuffer나 Heap에 데이터를 복제하는 작업
Interceptor에 두어야 할 것 (Domain-specific Concerns)
- Detailed Authorization: "이 유저가 이 게시물의 수정 권한이 있는가?"처럼 DB 조회나 복잡한 비즈니스 조건이 필요한 권한 체크
- API Metrics: 특정 컨트롤러 메서드의 실행 시간 측정이나 호출 통계 수집
- View Manipulation: 모델 데이터를 컨트롤러 이후에 공통적으로 수정해야 하는 경우
4. 결론
결국 필터와 인터셉터 중 무엇을 쓸 것인가에 대한 고민은 "내가 다루려는 자원의 성질을 바꿀 것인가, 아니면 활용할 것인가"로 요약할 수 있다.
'Architecture' 카테고리의 다른 글
| WebFlux의 내부 파이프라인, Publisher의 구성과 내부 작동 방식 (1) | 2026.01.02 |
|---|---|
| 네트워크/IO 퍼포먼스 (Zero-Copy) (3) | 2025.12.21 |
| 커넥션 풀의 내부 최적화 (HikariCP) (0) | 2025.12.18 |
| AI와 Chaining으로 데이터 없는 검색 서비스 구현 (0) | 2025.12.06 |
| 대규모 채팅 서비스 개발에 대한 회고 (2) | 2025.07.15 |