9. DispatcherServlet → HandlerMapping: getHandler()
DispatcherServlet의 doDispatch() 안에서 어떤 컨트롤러를 실행할지 결정하는 첫 번째 핵심 단계가 getHandler() 입니다. 이 단계는 단순히 컨트롤러 하나를 찾는 것이 아니라, 등록된 여러 HandlerMapping 중에서 현재 요청을 처리할 수 있는 매핑 전략을 순서대로 탐색하고, 그 결과를 HandlerExecutionChain 으로 감싸 반환합니다.
전체 흐름 요약
- DispatcherServlet 초기화 시점에
HandlerMapping빈들을 수집한다. - 수집된
HandlerMapping들은 정렬되어 우선순위를 가진다. - 요청이 들어오면
DispatcherServlet.getHandler()가 목록을 순서대로 순회한다. - 가장 먼저 매칭된
HandlerMapping이HandlerExecutionChain을 반환한다. - 이 체인 안에는 실제 핸들러와 함께 실행할 인터셉터 목록이 들어 있다.
실제 코드 흐름 분석
1) DispatcherServlet 초기화 시 HandlerMapping 목록 준비
// DispatcherServlet.java
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
}
}
핵심은 HandlerMapping 이 하나가 아니라 List<HandlerMapping> 이라는 점입니다. 그리고 Spring은 이 목록을 정렬해 우선순위 순서대로 유지합니다.
2) 실제 요청 시 getHandler()가 모든 HandlerMapping을 순회
// DispatcherServlet.java
protected @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
이 메서드는 매우 단순하지만 설계적으로 중요합니다.
- 모든
HandlerMapping을 순서대로 확인한다. - 각 매핑 전략은 현재 요청을 처리할 수 있으면
HandlerExecutionChain을 반환한다. - 가장 먼저
null이 아닌 값을 반환한 매핑이 승리한다. - 끝까지 못 찾으면
null이고, 이후noHandlerFound()흐름으로 간다.
3) HandlerMapping의 역할은 “요청 → 핸들러 체인” 매핑
// HandlerMapping.java
public interface HandlerMapping {
@Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
HandlerMapping 인터페이스의 핵심은 “현재 요청을 처리할 수 있는 핸들러와 인터셉터 묶음”을 찾는 것입니다.
주석에도 이런 의도가 드러납니다.
- Spring은
BeanNameUrlHandlerMapping,RequestMappingHandlerMapping같은 여러 구현체를 제공한다. - 구현체는
Ordered를 통해 우선순위를 가질 수 있다. - 반환값은 단순 handler가 아니라
HandlerExecutionChain이다.
4) 반환값이 handler가 아니라 HandlerExecutionChain인 이유
// HandlerExecutionChain.java
public class HandlerExecutionChain {
private final Object handler;
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
public Object getHandler() {
return this.handler;
}
public List<HandlerInterceptor> getInterceptorList() {
return (!this.interceptorList.isEmpty() ? Collections.unmodifiableList(this.interceptorList) :
Collections.emptyList());
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
}
즉 Step 9에서 DispatcherServlet이 받는 것은 단순한 컨트롤러 메서드가 아닙니다.
- 실제 실행 대상 handler
- 그 앞뒤에서 동작할 interceptor 목록
이 둘을 하나로 묶은 실행 단위가 HandlerExecutionChain 입니다.
왜 이렇게 설계했을까?
1) HandlerMapping을 하나로 고정하지 않기 위해
Spring MVC는 요청 매핑 전략이 하나가 아닙니다.
@RequestMapping기반 매핑- 빈 이름 기반 URL 매핑
- 정적 리소스 매핑
- 사용자 정의 매핑
이런 여러 전략을 동시에 등록하고, 우선순위에 따라 선택할 수 있어야 합니다. 그래서 DispatcherServlet 은 특정 구현체 하나를 알지 않고 List<HandlerMapping> 만 순회합니다.
2) 인터셉터 적용을 매핑 결과에 포함하기 위해
DispatcherServlet 입장에서 필요한 것은 단순히 “누가 처리하느냐” 만이 아닙니다. 실제로는:
- 어떤 핸들러가 실행되는가?
- 그 전에 어떤 인터셉터들이 실행되는가?
- 이후 어떤 postHandle/afterCompletion 이 붙는가?
그래서 반환값이 handler 가 아니라 HandlerExecutionChain 인 것입니다.
3) first match wins 구조를 만들기 위해
getHandler() 구현을 보면 첫 번째 매칭 결과를 즉시 반환합니다.
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
이 구조 덕분에 Spring MVC는 여러 매핑 전략을 조합하면서도 충돌 시 우선순위를 명확히 유지할 수 있습니다.
시퀀스 다이어그램
여기서 꼭 잡아야 할 포인트
DispatcherServlet은 컨트롤러를 직접 찾지 않는다.- 실제 탐색 책임은 각
HandlerMapping구현체에 있다. - Step 9의 결과물은
handler하나가 아니라HandlerExecutionChain이다. - Step 11에서
preHandle()이 바로 실행될 수 있는 이유도, Step 9에서 이미 인터셉터 목록이 같이 반환되었기 때문이다.
탐구 질문
- 왜 Spring은
HandlerMapping을 하나의 구현으로 고정하지 않고List로 관리했을까요? - 왜 반환 타입이 단순
Object handler가 아니라HandlerExecutionChain일까요? - 만약
RequestMappingHandlerMapping이 매칭되기 전에 다른HandlerMapping이 먼저 매칭된다면, 전체 요청 흐름은 어떻게 달라질까요?
← 이전: 8. FrameworkServlet → DispatcherServlet | 다음: 10. HandlerMapping → DispatcherServlet