지난 글에서는 왜 Spring MVC에 HandlerAdapter라는 개념이 필요한가를 살펴봤다.
이번 글에서는 한 발짝 더 들어가, 실제로 존재하는 HandlerAdapter 구현체들은 어떤 것들이 있고, 각각 어떤 차이가 있는지 정리해보자.
HandlerAdapter 인터페이스의 기본 역할
다시 한 번 확인하자.
모든 HandlerAdapter는 HandlerAdapter 인터페이스를 구현해야 하며, 핵심 메소드는 두 가지다.
public interface HandlerAdapter {
/**
* Whether this {@code HandlerAdapter} supports the given {@code handler}.
* @param handler the handler object to check
* @return whether the handler is supported
*/
boolean supports(Object handler);
/**
* Handle the request with the given handler, previously checked via
* {@link #supports(Object)}.
* <p>Implementations should consider the following for exception handling:
* <ul>
* <li>Handle invocation exceptions within this method.
* <li>{@link HandlerResult#setExceptionHandler(DispatchExceptionHandler)
* Set an exception handler} on the returned {@code HandlerResult} to handle
* deferred exceptions from asynchronous return values, and to handle
* exceptions from response rendering.
* <li>Implement {@link DispatchExceptionHandler} to extend exception
* handling to exceptions that occur before a handler is selected.
* </ul>
* @param exchange current server exchange
* @param handler the selected handler which must have been previously
* checked via {@link #supports(Object)}
* @return {@link Mono} that emits a {@code HandlerResult}, or completes
* empty if the request is fully handled; any error signal would not be
* handled within the {@link DispatcherHandler}, and would instead be
* processed by the chain of registered
* {@link org.springframework.web.server.WebExceptionHandler}s at the end
* of the {@link org.springframework.web.server.WebFilter} chain
*/
Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler);
}
- supports() : 이 어댑터가 해당 핸들러를 지원할 수 있는지 확인
- handle() : 지원하는 경우, 실제로 핸들러를 실행하고 ModelAndView를 반환
앞선 글에서 말했듯이, DispatcherServlet은 단순히 모든 HandlerAdapter를 우선순위대로 돌면서 supports() → handle()을 호출한다.
주요 구현체들
1. HttpRequestHandlerAdapter
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
@Override
public @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
}
- 지원하는 컨트롤러: HttpRequestHandler 인터페이스
- 특징: 응답을 컨트롤러가 직접 작성 (Servlet API 스타일)
- 리턴: 항상 null (즉, 뷰 렌더링 단계로 가지 않음)
- 사용 사례: 이미지, 파일 다운로드, API 응답을 직접 쓰는 경우
2. SimpleControllerHandlerAdapter
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
@Override
public @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
}
- 지원하는 컨트롤러: org.springframework.web.servlet.mvc.Controller 인터페이스
- 특징: 반드시 ModelAndView를 반환
- 리턴: ModelAndView → DispatcherServlet이 ViewResolver를 통해 뷰 렌더링
- 사용 사례: 스프링 초창기 방식, 지금은 거의 사용되지 않음
3. RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
@Override
protected @Nullable ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
- 지원하는 컨트롤러: @Controller, @RequestMapping, @GetMapping, @PostMapping 등 어노테이션 기반 컨트롤러
- 특징:
- 다양한 리턴 타입 지원 (ModelAndView, String, ResponseEntity, Object, void 등)
- 내부적으로는 HandlerMethodReturnValueHandler 체인을 통해 리턴 값을 표준 ModelAndView로 변환
- AbstractHandlerMethodAdapter를 상속하여 handleInternal, supportsInternal을 오버라이딩 (템플릿 메소드 패턴 구조)
- 리턴: 최종적으로 ModelAndView 또는 null
- 사용 사례: 현재 Spring MVC의 사실상 표준. 우리가 흔히 쓰는 모든 @Controller 기반 로직이 여기에 해당
패키지 구조 차이
코드 레벨에서 살펴보면, 구현체들이 서로 다른 패키지에 위치한다.
- HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter → org.springframework.web.servlet.mvc
- RequestMappingHandlerAdapter → org.springframework.web.servlet.mvc.method.annotation
이 차이는 역사적 배경 때문이다.
- mvc 패키지는 레거시 컨트롤러 지원 (Spring 초창기부터 존재)
- mvc.method.annotation 패키지는 어노테이션 기반 MVC 지원 (추후 추가)
즉, 호환성과 책임 분리를 위해 구조적으로 구분해 둔 것이다.
Ordered와 우선순위
모든 HandlerAdapter는 동시에 존재할 수 있고, 어떤 순서로 실행할지는 Ordered 인터페이스(또는 @Order)로 제어한다.
- 구현하지 않으면 기본적으로 LOWEST_PRECEDENCE (= Integer.MAX_VALUE)
- DispatcherServlet은 이 순서대로 어댑터를 검사하며, supports(handler)가 true인 첫 번째 어댑터를 실행한다.
정리
- HttpRequestHandlerAdapter: 컨트롤러가 직접 응답을 쓰고, 항상 null 반환
- SimpleControllerHandlerAdapter: 레거시 Controller → 반드시 ModelAndView 반환
- RequestMappingHandlerAdapter: 현대 MVC 표준, 다양한 리턴 타입을 수용하고 내부적으로 ModelAndView로 표준화
즉, HandlerAdapter 구현체들은 서로 다른 컨트롤러 모델을 위한 다리 역할을 한다.
레거시부터 최신 방식까지, 다양한 컨트롤러 스타일을 모두 수용할 수 있었던 이유가 바로 여기에 있다.
참고
Spring 스펙 공식문서
1. HandlerAdapter : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/HandlerAdapter.html
2. SimpleControllerHandlerAdapter : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.html
3. HttpRequestHandlerAdapter : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.html
4. RequestMappingHandlerAdapter : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.html
Spring 공식 깃허브
1. HandlerAdapter : https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/HandlerAdapter.java
2. SimpleControllerHandlerAdapter : https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/SimpleControllerHandlerAdapter.java
3. HttpRequestHandlerAdapter : https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.java
4. RequestMappingHandlerAdapter : https://github.com/spring-projects/spring-framework/blob/main/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java#L826
'Spring' 카테고리의 다른 글
| [Spring MVC] HandlerAdapter 가 존재하는 이유는 뭘까? (1) | 2025.10.03 |
|---|