티스토리 뷰

Dev/Spring

@Controller & @RestController

kuidoli 2024. 8. 29. 13:57

Controller 란

In Spring’s approach to building web sites, HTTP requests are handled by a controller. You can easily identify the controller by the @Controller annotation

In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller. These components are identified by the @RestController annotation

controller 는 스프링에서 HTTP 요청(과 응답, 예외 등)을 처리한다. controller 임을 나타내기 위해 어노테이션을 사용할 수 있다. @RestController 는 @Controller 와 함께 Annotated Controllers 에 해당한다.

HTTP 요청은 HTTP Messages 를 통해 이루어지는데 이에 대해서는 따로 정리했다.


@RestController 란

@Controller 가 화면에 해당하는 View 를 반환하기 위해 사용한다면 @RestController 는 data 를 반환하기 위해 사용한다. @Controller 도 데이터를 반환할 수 있으나 또 다른 annotation 인 @ResponseBody 를 사용해야 한다.


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(
        annotation = Controller.class
    )
    String value() default "";
}

RestController 코드를 확인해보면 @Controller, @ResponseBody 를 담고 있다. @Controller 와 @ResponseBody 를 선언할 필요없이 @RestController 하나만 선언해서 데이터를 반환할 수도 있다.


@Controller 가 View 를 반환하는 과정

controller
// org.springframework.web.servlet.DispatcherServlet.class
// (필요한 부분만 나타내기 위해 많은 부분이 생략 됐습니다)
public class DispatcherServlet extends FrameworkServlet {
  @Nullable
  private List<HandlerMapping> handlerMappings;

  @Nullable
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
      Iterator var2 = this.handlerMappings.iterator();

      while(var2.hasNext()) {
        HandlerMapping mapping = (HandlerMapping)var2.next();
        HandlerExecutionChain handler = mapping.getHandler(request);
        if (handler != null) {
          return handler;
        }
      }
    }

    return null;
  }
}

프론트 컨트롤러 (Dispatcher Servlet) 가 요청을 받으면 해당 요청을 처리할 컨트롤러(핸들러)를 handlerMappings 에서 찾는다. 이를 Handler Mapping 이라고 한다.


// org.springframework.web.servlet.DispatcherServlet.class
// (필요한 부분만 나타내기 위해 많은 부분이 생략 됐습니다)

public class DispatcherServlet extends FrameworkServlet {
  @Nullable
  private List<HandlerAdapter> handlerAdapters; 

  protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
      Iterator var2 = this.handlerAdapters.iterator();

      while(var2.hasNext()) {
        HandlerAdapter adapter = (HandlerAdapter)var2.next();
        if (adapter.supports(handler)) {
          return adapter;
        }
      }
    }

    throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  }

  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    ModelAndView mv = null;
    Exception dispatchException = null;

    // handler(컨트롤러) 조회
    mappedHandler = this.getHandler(processedRequest);
    if (mappedHandler == null) {
      this.noHandlerFound(processedRequest, response);
      return;
    }

    // handlerAdapter 를 조회
    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    // handlerAdatper 는 ModelAndView 타입을 반환
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
  }
}

Handler Mapping 을 통해 컨트롤러를 찾으면 Handler Adapter 에게 컨트롤러의 실행을 위임한다. Controller 가 한 종류라면 Handler Mapping 으로 찾은 컨트롤러를 직접 실행해도 문제가 없겠지만 서로 다른 종류의 컨트롤러를 사용하게 되면 컨트롤러를 종류별로 관리해줄 대상이 필요하다.

이 역할을 Handler Adapter 가 한다. 서로 다른 종류의 컨트롤러들을 각각의 Adapter 로 관리하고 이 Adapter 들을 Handler Adapter 가 관리한다. 어떤 컨트롤러는 ModelAndView 를 리턴하고, 어떤 컨트롤러는 그렇지 않을 수 있다. Handler Adapter 가 공통된 리턴 타입으로 반환되도록 조정한다.

프론트 컨트롤러는 Handler Adapter 를 통해 컨트롤러를 실행하고 반환된 ModelAndView 객체를 활용한다.


// org.springframework.web.servlet.DispatcherServlet.class
// (필요한 부분만 나타내기 위해 많은 부분이 생략 됐습니다)

public class DispatcherServlet extends FrameworkServlet {
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
  }

  private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    if (mv != null && !mv.wasCleared()) {
      this.render(mv, request, response);
    }
  }

  protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ModelAndView 객체에서 viewName 을 조회한다
    String viewName = mv.getViewName();
    View view;
    // viewName 이 null 이 아니면 viewResolvers 를 통해 View 객체를 얻는다
    if (viewName != null) {
      view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
    // viewName 이 null 이면 ModelAndView 객체에서 View 객체를 얻는다
    } else {
      view = mv.getView();
    }

    try {
      view.render(mv.getModelInternal(), request, response);
    } catch (Exception var8) {
      Exception ex = var8;
      throw ex;
    }
  }

  @Nullable
  protected View resolveViewName(String viewName, @Nullable Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
      Iterator var5 = this.viewResolvers.iterator();

      while(var5.hasNext()) {
        ViewResolver viewResolver = (ViewResolver)var5.next();
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
          return view;
        }
      }
    }

    return null;
  }
}

프론트 컨트롤러는 ModelAndView 객체에서 viewName 을 조회한다. viewName 이 null 이 아니면 viewResolvers 를 통해 적절한 viewResolver 를 찾고 viewResolver 를 통해 View 객체를 얻는다. 얻은 View 객체의 render 함수를 호출한다.


@RestController 가 data 를 반환하는 과정

restcontroller

소스코드를 보다보니 Handler Adapter 는 ModelAndView 를 반환하는데 어떻게 @RestController 가 View 를 렌더링하지 않고 Data 를 리턴할 수 있는지 의문이 들었다.


결론적으로 @ResponseBody 어노테이션은 Handler Adapter 로 RequestMappingHandlerAdapter 가 실행이 되는데, RequestMappingHandlerAdapter 는 HandlerMethodReturnValueHandler 를 @ResponseBody 어노테이션이 사용된 경우(@RestController 는 @ResponseBody 어노테이션을 포함) RequestResponseBodyMethodProcessor 를 실행한다. RequestResponseBodyMethodProcessor 가 ModelAndView 를 null 을 반환하도록 처리해서 RequestMappingHandlerAdapter 에서는 ModelAndView 를 null 을 반환한다. ModelAndView 가 null 이라 render 할 대상이 없어서 viewResolvers 를 찾지 않는다.

@RestController 와 관련된 내용들을 보면서 어떻게 ModelAndView 가 null 이 반환되는지 알아보려고 한다. 그리고 어떻게 @RestController 는 View 를 렌더링하는게 아니라 Data 를 리턴할 수 있는지 알아보려고 한다.

아래에서 보게될 소스코드는 필요한 부분만 볼 수 있도록 많은 부분이 생략 됐습니다.


RequestMappingHandlerMapping

스프링 부트 3.0(스프링 프레임워크 6.0)부터는 클래스 레벨에 @RequestMapping 이 있어도 스프링 컨트롤러로 인식하지 않는다. 오직 @Controller 가 있어야 스프링 컨트롤러로 인식한다. 참고로 @RestController 는 해당 애노테이션 내부에 @Controller 를 포함하고 있으므로 인식 된다. 따라서 @Controller 가 없는 위의 두 코드는 스프링 컨트롤러로 인식되지 않는다. ( RequestMappingHandlerMapping 에서 @RequestMapping 는 이제 인식하지 않고, Controller 만 인식한다.) 스프링 MVC 1편


HandlerExecutionChain 조회

public class DispatcherServlet extends FrameworkServlet {
  @Nullable
  private List<HandlerMapping> handlerMappings;

  @Nullable
  protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {}
}
// org.springframework.web.servlet.HandlerMapping.class
public interface HandlerMapping {
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
// org.springframework.web.servlet.handler.AbstractHandlerMapping.class
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered, BeanNameAware {
  @Nullable
  public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    Object handler = this.getHandlerInternal(request);

    HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
    return executionChain;
  }

  protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    HandlerExecutionChain var10000;
    if (handler instanceof HandlerExecutionChain handlerExecutionChain) {
      var10000 = handlerExecutionChain;
    } else {
      var10000 = new HandlerExecutionChain(handler);
    }

    HandlerExecutionChain chain = var10000;
    Iterator var7 = this.adaptedInterceptors.iterator();

    while(var7.hasNext()) {
      HandlerInterceptor interceptor = (HandlerInterceptor)var7.next();
      if (interceptor instanceof MappedInterceptor mappedInterceptor) {
        if (mappedInterceptor.matches(request)) {
          chain.addInterceptor(mappedInterceptor.getInterceptor());
        }
      } else {
        chain.addInterceptor(interceptor);
      }
    }

    return chain;
  }
}

우선 getHandler 메소드가 DispatcherServlet 에도 있고 HandlerMapping 인터페이스에도 있다는 점에 유의해야 한다.

DispatcherServlet 의 getHandler 메소드가 는 HandlerExecutionChain 을 반환한다. DispatcherServlet 의 getHandler 메소드는 내부적으로 HandlerMapping 객체를 찾는데 @Controller 는 HandlerMapping 인터페이스의 구현체인 RequestMappingHandlerMapping 객체를 찾는다.

HandlerMapping 인터페이스에는 getHandler 가 있고 이를 AbstractHandlerMapping 가 구현했다. RequestMappingHandlerMapping 의 상위 객체로 RequestMappingHandlerMapping 가 getHandler 를 호출하면 부모 객체의 메소드 탐색을 하면서 실행된다. getHandler 메소드를 실행하면서 getHandlerExecutionChain 도 내부적으로 실행해서 HandlerExecutionChain 을 반환한다.


RequestMappingHandlerAdapter

DispatcherServlet 에서 Handler Adapter 를 조회(@Controller 의 경우 RequestMappingHandlerAdapter 가 반환됨) 후 handle 메서드를 호출한다.


handler 실행

// org.springframework.web.servlet.HandlerExecutionChain.class
public class HandlerExecutionChain {
  private final Object handler;
}
public class DispatcherServlet extends FrameworkServlet {
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerExecutionChain mappedHandler = null;
    mappedHandler = this.getHandler(processedRequest);
    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  }
}
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.class
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
  protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    mav = this.invokeHandlerMethod(request, response, handlerMethod);
  }
}

DispatcherServlet 은 RequestMappingHandlerMapping 을 통해 얻은 handler 의 실행을 HandlerAdapter 에게 위임한다. handler 는 HandlerExecutionChain 에 변수로 등록되어 있는데, HandlerExecutionChain 는 DispatcherServlet 이 Handler Mapping 을 통해 RequestMappingHandlerMapping 로부터 얻게된다. @Controller 의 경우 handler 는 클래스가 아니라 실행할 메소드다.

즉, handle 메소드를 RequestMappingHandlerAdapter 가 실행하게 된다. handle 메소드를 실행하면서 넣은 HandlerExecutionChain 의 getHandler 인자(mappedHandler.getHandler())는 RequestMappingHandlerAdapter 의 handleInternal 메소드의 파라미터를 보면 HandlerMethod 로 선언되어 있다.

HandlerAdapter 인터페이스의 handle 메서드는 RequestMappingHandlerAdapter 에는 없고 부모 클래스인 AbstractHandlerMethodAdapter 에 정의되어 있다. handle 메서드는 handleInternal 를 호출하는데 이는 RequestMappingHandlerAdapter 에 구현되어 있다. handleInternal 는 invokeHandlerMethod 를 호출하고 ModelAndView 를 반환한다.


ModelAndView 반환

public class DispatcherServlet extends FrameworkServlet {
  protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
      Iterator var2 = this.handlerAdapters.iterator();

      while(var2.hasNext()) {
        HandlerAdapter adapter = (HandlerAdapter)var2.next();
        if (adapter.supports(handler)) {
          return adapter;
        }
      }
    }

    throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  }
}

getHandlerAdapter 메서드에서 Handler Adapter 를 조회하면 RequestMappingHandlerAdapter 를 반환한다.

RequestMappingHandlerAdapter 는 @Controller 어노테이션을 사용하는 경우 반환된다. @RestController 는 @Controller 를 포함하기 때문에 마찬가지로 RequestMappingHandlerAdapter 가 반환된다.


public class DispatcherServlet extends FrameworkServlet {
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // handlerAdapter 를 조회
    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    // handlerAdatper 는 ModelAndView 타입을 반환
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  }
}
// org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.class
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
  @Nullable
  public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    // Object 로 선언한 handler 를 HandlerMethod 로 캐스팅한다
    return this.handleInternal(request, response, (HandlerMethod)handler);
  }
}
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
  protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    ModelAndView mav;
    mav = this.invokeHandlerMethod(request, response, handlerMethod);
    return mav;
  }

  @Nullable
  protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // HandlerMethod 를 ServletInvocableHandlerMethod 로 변환한다
    ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
    invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object[0]);
  }
}

RequestMappingHandlerAdapter 는 invocableMethod를 실행하는데 invocableMethod 는 handler 메소드를 토대로 생성한 객체다.


public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
  public void afterPropertiesSet() {
    if (this.returnValueHandlers == null) {
      resolvers = this.getDefaultReturnValueHandlers();
      // 여러 종류의 HandlerMethodReturnValueHandler 를 모은 resolvers 리스트를 HandlerMethodReturnValueHandlerComposite 객체에 등록하고 
      // HandlerMethodReturnValueHandlerComposite 객체를 returnValueHandlers 로 지정한다
      this.returnValueHandlers = (new HandlerMethodReturnValueHandlerComposite()).addHandlers(resolvers);
    }
  }

  // RequestResponseBodyMethodProcessor 외에 다양한 객체들을 handlers 에 등록한다
  private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList(20);

    handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));

    return handlers;
  }
}

ServletInvocableHandlerMethod 의 invokeAndHandle 를 보기 전에 먼저 봐야할 것이 있다.

RequestMappingHandlerAdapter 는 응답 후 처리를 할 HandlerMethodReturnValueHandler 들을 ServletInvocableHandlerMethod 에 설정 해준다.

적절한 HandlerMethodReturnValueHandler 객체의 실행은 HandlerMethodReturnValueHandlerComposite 에서 수행하고, HandlerMethodReturnValueHandlerComposite 의 수행을 ServletInvocableHandlerMethod 에서 한다.


// org.springframework.web.servlet.mvc.method.ServletInvocableHandlerMethod.class
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
  public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    Object returnValue = this.invokeForRequest(webRequest, mavContainer, providedArgs);

    try {
      // HandlerMethodReturnValueHandlerComposite 의 handleReturnValue 를 호출한다
      this.returnValueHandlers.handleReturnValue(returnValue, this.getReturnValueType(returnValue), mavContainer, webRequest);
    } catch (Exception var6) {
    }
  }
}

ServletInvocableHandlerMethod 의 invokeAndHandle 로 돌아왔다. RequestMappingHandlerAdapter 에서 설정해준 returnValueHandlers 의 handleReturnValue 메소드를 호출한다.


// org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.class
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // HandlerMethodReturnValueHandler 를 선택
    HandlerMethodReturnValueHandler handler = this.selectHandler(returnValue, returnType);
    if (handler == null) {
      throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    } else {
      // HandlerMethodReturnValueHandler 의 handleReturnValue 를 실행
      handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
    }
  }

  @Nullable
  private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    boolean isAsyncValue = this.isAsyncReturnValue(value, returnType);
    Iterator var4 = this.returnValueHandlers.iterator();

    HandlerMethodReturnValueHandler handler;
    do {
      do {
        if (!var4.hasNext()) {
          return null;
        }

        handler = (HandlerMethodReturnValueHandler)var4.next();
      } while(isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler));
    } while(!handler.supportsReturnType(returnType));

    return handler;
  }
}
// org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.class
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  public boolean supportsReturnType(MethodParameter returnType) {
    return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class);
  }
}

HandlerMethodReturnValueHandlerComposite 은 자신이 호출할 HandlerMethodReturnValueHandler 를 찾는데 @ResponseBody 어노테이션이 클래스 레벨이나 메소드 레벨에 있으면 RequestResponseBodyMethodProcessor 가 선택된다.


public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    // 이곳에서 requestHandled 를 true 로 처리
    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = this.createInputMessage(webRequest);
    // Response 생성하고 이 객체에 write 한다
    ServletServerHttpResponse outputMessage = this.createOutputMessage(webRequest);
    if (returnValue instanceof ProblemDetail detail) {
      outputMessage.setStatusCode(HttpStatusCode.valueOf(detail.getStatus()));
      if (detail.getInstance() == null) {
        URI path = URI.create(inputMessage.getServletRequest().getRequestURI());
        detail.setInstance(path);
      }
    }

    this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
  }
}
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
  @Nullable
  private ModelAndView getModelAndView(ModelAndViewContainer mavContainer, ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
    // RequestResponseBodyMethodProcessor 가 
    // requestHandled 를 true 로 처리해서 null 을 반환한다
    if (mavContainer.isRequestHandled()) {
      return null;
    } else {
    }
  }
}

RequestResponseBodyMethodProcessor 에서 ModelAndViewContainer 의 requestHandled 변수의 값을 true 로 설정한다. 그리고 RequestMappingHandlerAdapter 에서는 requestHandled 의 값을 isRequestHandled 함수로 확인한다. isRequestHandled 가 true 면 null 을 리턴한다. ModelAndView 타입을 반환하는 getModelAndView 함수는 null 을 반환한다.


public class DispatcherServlet extends FrameworkServlet {
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
  }

  private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    // @RestController 의 경우 이 if 문을 충족하지 않아서 render 로직을 실행하지 않는다
    if (mv != null && !mv.wasCleared()) {
      this.render(mv, request, response);
      if (errorView) {
        WebUtils.clearErrorRequestAttributes(request);
      }
    } else if (this.logger.isTraceEnabled()) {
      this.logger.trace("No view rendering, null ModelAndView returned.");
    }

    if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      if (mappedHandler != null) {
        // client 에게 response 를 반환하고 나머지 처리를 진행한다
        mappedHandler.triggerAfterCompletion(request, response, (Exception)null);
      }

    }
  }
}

HandlerAdapter 의 handle 메소드 이후에 DispatcherServlet 은 processDispatchResult 를 호출한다. processDispatchResult 를 호출하면서 handle 함수의 결과로 받은 ModelAndView 객체를 인자로 넘겨주는데 앞서 이 값은 null 임을 확인했다.

mv 가 null 이라서 mv != null && !mv.wasCleared() 조건을 만족하지 못하고 render 함수를 호출하지 않는다.


HttpMessageConverter

@RestController 는 RequestMappingHandlerAdapter 에서 반환되는 ModelAndView 가 null 이라 DispatcherServlet 가 View 를 그리지 않는다는 것은 알게 됐다.

이제 어떻게 View 렌더링 대신 Data (Response) 가 반환 되는지 알아보는게 남았다.


public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
  public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
  }
  // 부모 클래스인 AbstractMessageConverterMethodProcessor 의 writeWithMessageConverters 를 호출해서 리턴할 data 를 설정한다
    this.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
  }
}
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
  protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    // 적절한 HttpMessageConverter 를 찾는다
    HttpMessageConverter converter;
    GenericHttpMessageConverter genericConverter;
    while(true) {
      if (!var21.hasNext()) {
        break label166;
      }

      converter = (HttpMessageConverter)var21.next();
      if (genericConverter != null) {
      // HttpMessageConverter 구현체 중에서 어떤 구현체가 처리 가능한지 조회한다
      } else if (converter.canWrite(valueType, selectedMediaType)) {
        break;
      }
    }

    // 찾은 구현체의 write 메소드를 호출하고 인자로 담은 outputMessage 에 write 한다
    converter.write(body, selectedMediaType, outputMessage);
}
public class ServletServerHttpResponse implements ServerHttpResponse {
  private final HttpServletResponse servletResponse;
  private final HttpHeaders headers;
}

RequestResponseBodyMethodProcessor 는 부모 클래스인 AbstractMessageConverterMethodProcessor 의 writeWithMessageConverters 를 호출한다. AbstractMessageConverterMethodProcessor 는 응답을 처리할 HttpMessageConverter 를 찾고, RequestResponseBodyMethodProcessor 가 생성해서 넘겨준 ServletServerHttpResponse 객체에 HttpMessageConverter 가 write 하도록 호출한다. 최종적으로는 Servlet Container 가 Data (Response) 를 클라이언트에게 반환한다.


<참고>

https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages

https://martinfowler.com/eaaCatalog/frontController.html

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet.html

https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller.html

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html

https://spring.io/guides/gs/serving-web-content#initial

https://mangkyu.tistory.com/49

https://mangkyu.tistory.com/216

https://duooo-story.tistory.com/9

https://happy-coding-day.tistory.com/191

스프링 MVC 1편

ChatGPT

'Dev > Spring' 카테고리의 다른 글

JPA - N + 1 (2)  (1) 2024.11.05
JPA - N+1 문제  (0) 2024.09.20
JPA - @JoinColumn  (0) 2024.04.11
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함