Deep Dive /Java Web /10

10. HandlerMapping → DispatcherServlet: HandlerExecutionChain 반환

HandlerMapping.getHandler()HandlerExecutionChain 을 반환합니다. 이것은 단순히 핸들러(컨트롤러 메서드)만 반환하는 것이 아니라, 해당 요청을 처리할 때 필요한 모든 것을 한 번에 패킹하여 반환합니다.


시퀀스 다이어그램

sequenceDiagram participant DispatcherServlet participant HandlerMapping participant Chain as HandlerExecutionChain DispatcherServlet->>HandlerMapping: getHandler(request) HandlerMapping->>HandlerMapping: 핸들러 매칭 HandlerMapping->>HandlerMapping: 적용 인터셉터 수집 HandlerMapping->>Chain: new(handler, interceptors...) Chain-->>HandlerMapping: 생성 완료 HandlerMapping-->>DispatcherServlet: HandlerExecutionChain 반환 Note over DispatcherServlet,Chain: chain = { handler + interceptorList }

HandlerExecutionChain이란?

HandlerExecutionChain = {
  handler: 실제 실행할 컨트롤러 메서드,
  interceptorList: [인터셉터1, 인터셉터2, ...]
}

“핸들러와 인터셉터를 한 묶음으로 반환” 하는 이유는 무엇일까요?


왜 이런 설계를 했을까?

문제 상황: 핸들러만 반환한다면?

// ❌ 나쁜 설계: 핸들러만 반환
handler = getHandler();

// 그럼 인터셉터는?
// 1) 어디서 가져오지?
// 2) 어느 순서로 실행하지?
// 3) 같은 요청에 필요한 여러 인터셉터는 어떻게 조합하지?

해결책: 체인으로 함께 반환

// ✅ 좋은 설계: 핸들러 + 인터셉터를 함께 반환
HandlerExecutionChain chain = getHandler();

// 이미 조합되어 있으니:
// 1) handler = chain.getHandler()  ← 핸들러 꺼내기
// 2) chain.applyPreHandle()        ← 인터셉터들 정방향 실행
// 3) chain.applyPostHandle()       ← 인터셉터들 역방향 실행

실제 코드 분석

HandlerExecutionChain의 구조

// HandlerExecutionChain.java
public class HandlerExecutionChain {
    
    private final Object handler;  // 실제 컨트롤러 메서드
    
    @Nullable
    private HandlerInterceptor[] interceptors;  // 인터셉터 배열
    
    private List<HandlerInterceptor> interceptorList;  // 인터셉터 리스트
    
    private int interceptorIndex = -1;  // 마지막 실행된 인터셉터의 인덱스
    
    // 생성자: 핸들러와 인터셉터들을 한 번에 받음
    public HandlerExecutionChain(Object handler, @Nullable HandlerInterceptor... interceptors) {
        this.handler = handler;
        this.interceptors = interceptors;
    }
}

HandlerMapping에서 체인 생성

// RequestMappingHandlerMapping.java (구현 클래스의 예)
@Override
@Nullable
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    
    // 1️⃣ HandlerExecutionChain 생성 (핸들러만 먼저)
    HandlerExecutionChain chain = new HandlerExecutionChain(handler);
    
    // 2️⃣ 해당 핸들러에 적용되는 모든 인터셉터를 추가
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor.matches(request, handler)) {
            chain.addInterceptor(interceptor);
        }
    }
    
    // 3️⃣ 완성된 체인 반환
    return chain;
}

핵심: 왜 “샌드위치” 구조가 가능한가?

HandlerExecutionChain이 handler와 interceptor를 함께 반환하기 때문입니다.

┌─────────────────────────────────────┐
│ applyPreHandle()  (정방향)          │  ← 인터셉터 1, 2, 3 순서로
├─────────────────────────────────────┤
│       handler.execute()             │  ← 컨트롤러 실행
├─────────────────────────────────────┤
│ applyPostHandle()  (역방향)         │  ← 인터셉터 3, 2, 1 순서로
└─────────────────────────────────────┘

이 모든 것이 하나의 chain 객체에 담겨 있어서 가능합니다!


🤔 탐구 질문

Step 10을 이해했다면, 이제 궁금한 점:

  1. preHandle과 postHandle이 역방향인 이유는?
    • 인터셉터 3개가 있을 때, preHandle은 1→2→3 순서로 실행되는데
    • postHandle은 3→2→1 순서로 실행되네요. 왜일까요?
  2. 만약 preHandle에서 인터셉터 2가 false를 반환하면?
    • 인터셉터 3은 실행이 안 되겠네요
    • 그럼 afterCompletion은 어떻게 되나요?

다음 단계인 Step 11: preHandle() 실행으로 넘어가기 전에, 위 질문들을 생각해보세요! 🧠