Spring

[Spring MVC] HandlerAdapter 의 구현체는 어떤것들이 있을까?

dragonwaterr 2025. 10. 7. 20:42

지난 글에서는 왜 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