요청을 처리기 사슬에 흘려보내 처리할 수 있는 객체가 받게 하는 패턴.
abstract class Handler {
private Handler next;
Handler setNext(Handler n) { this.next = n; return n; }
void handle(Request req) {
if (canHandle(req)) process(req);
else if (next != null) next.handle(req); // 못 하면 다음으로
}
}
// 사슬을 조립한다
auth.setNext(rateLimit).setNext(cache).setNext(controller);
무엇을 푸는가
if (조건A) 처리A else if (조건B) 처리B
-
else if (조건C) 처리C — ← 조건이 늘 때마다 이 코드를 고쳐야 한다
-
각 처리를 객체로 떼고 사슬로 연결하면
- 순서 변경·추가·제거가 조립부만 바꾸면 된다 (OCP)
두 가지 변형
-
순수형 — 하나가 처리하면 사슬이 끝난다 (예외 처리, 승인 요청)
-
변형형 — 전부가 순서대로 관여한다 (필터 체인, 미들웨어)
-
실무에서 흔한 것은 변형형이다
실무에서
javax.servlet.Filter · FilterChain // 서블릿 필터
Spring Security FilterChainProxy // 인증·인가 필터 사슬
OkHttp Interceptor · Netty ChannelPipeline
SLF4J·Logback 로거 계층 (부모로 전파)
대가
- 아무도 처리하지 않으면 요청이 조용히 사라진다 → 종단 처리기를 둔다
- 사슬이 길면 디버깅이 어렵다 (어디서 걸렸는지 추적)
- 순서 의존성이 생긴다 (인증이 캐시보다 앞에 있어야 한다)
면접 함정
- ❌ "모든 핸들러가 반드시 실행된다" → 순수형은 처리되면 멈춘다.
- ❌ Decorator와 혼동 → Decorator는 기능 추가, CoR은 처리 위임이 목적이다.
서블릿 필터로 감을 잡는다
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
log.info("before");
chain.doFilter(req, res); // ← 안 부르면 사슬이 여기서 끊긴다
log.info("after");
}
인증 실패 시 이걸 이용해 차단하고, 실수로 빠뜨리면 요청이 컨트롤러에 도달하지 않는다(흔한 버그).
순서가 곧 규칙이다
인증 → 인가 → 레이트리밋 → 캐시 → 컨트롤러
캐시가 인증보다 앞이면 → 인증 안 된 요청에 캐시된 응답을 준다 (보안 사고) 레이트리밋이 인증보다 앞이면 → 인증 비용 전에 차단해 자원을 아낀다 (의도적)
그래서 Spring Security는 필터 순서를 명시적으로 관리한다.
관찰 가능성
사슬이 길면 "어디서 걸렸나"를 알기 어렵다. 각 핸들러에 처리 여부·소요 시간을 남기고, 요청 ID를 사슬 전체에 전파한다(MDC·trace id).