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에서 이미 인터셉터 목록이 같이 반환되었기 때문이다.
다음 단계 연결
다음 문서 10번에서는 HandlerMapping이 단순 핸들러가 아니라 HandlerExecutionChain을 만들어 DispatcherServlet에 반환하는 이유를 봅니다.
← 이전: 8. FrameworkServlet → DispatcherServlet | 다음: 10. HandlerMapping → DispatcherServlet