Deep analysis of RestController and Controller

Posted by Parody on Sat, 20 Jul 2019 16:18:15 +0200

https://blog.csdn.net/qq_20597727/article/details/82347014

We all know that RestController only provides Rest style interface return value by default. RestController is used to annotate ontrollers that do not need to return pages. Here is a brief analysis of the differences between the two processing according to the source code.

@ The RestController source code is as follows.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {

   /**
    * The value may indicate a suggestion for a logical component name,
    * to be turned into a Spring bean in case of an autodetected component.
    * @return the suggested component name, if any
    * @since 4.0.1
    */
   String value() default "";

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

@ The way RestController is written depends on the combination of annotations. The @RestController is annotated by @Controller and @ResponseBody, indicating that @RestController has both annotation semantics. Therefore, in annotation processing, @RestController has more @ResponseBody semantics than @Controller, which is the difference between @RestController and @Controller. It is the reason why @RestController's return value is all transformed json.

So the summary is: @RestController = @Controller + @ResponseBody;

@ Processing of ResponseBody Annotations

Now that we know that the difference between @RestController and @Controller is an additional @ResponseBody semantics, let's look at the process of @ResponseBody.

First, you can see that @ResponseBody is an annotation for processing method return values. If you are familiar with the Spring MVC process, you can know that after the Handler Method is obtained from the request URL mapping, the Handler Adapter is the object of the Handler Method scheduling request method, the method call ends, and the scheduling object of the return value processing is also the Handler Adapter. Therefore, the processing of @ResponseBody annotations should also be done in Handler Adapter. (ps: It's not clear what Spring MVC can see In-depth analysis of Spring MVC)

In the RequestMappingHandlerAdapter#invokeHandlerMethod method, there are several more important lines of code

//Create Method Call Objects
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
//......
//Setting the Return Value Processor
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
//......
//Call method
invocableMethod.invokeAndHandle(webRequest, mavContainer);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

The variable returnValueHandlers listens to its name as if it were a set of processors dealing with return values. First, create an object that calls the method, then inject it into the processor, and finally call the method. This is a complete process.

We can then analyze whether our processor for the @ResponseBody annotation was initialized.

@ ResponseBody Annotation Processor Initialization

Search where returnValueHandlers are initialized, and you can see the call chain as follows:

  • RequestMappingHandlerAdapter#afterPropertiesSet

  • handlers = RequestMappingHandlerAdapter#getDefaultReturnValueHandlers

  • returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers)
    • 1

So when the bean initialization of RequestMapping Handler Adapter is completed, the return value handler is initialized and executed inside the RequestMapping Handler Adapter# getDefaultReturnValueHandlers method. The code is as follows.

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
   List<HandlerMethodReturnValueHandler> handlers = new ArrayList<HandlerMethodReturnValueHandler>();

   // Single-purpose return value types
   handlers.add(new ModelAndViewMethodReturnValueHandler());
   handlers.add(new ModelMethodProcessor());
   handlers.add(new ViewMethodReturnValueHandler());
   handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters()));
   handlers.add(new StreamingResponseBodyReturnValueHandler());
   handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
         this.contentNegotiationManager, this.requestResponseBodyAdvice));
   handlers.add(new HttpHeadersReturnValueHandler());
   handlers.add(new CallableMethodReturnValueHandler());
   handlers.add(new DeferredResultMethodReturnValueHandler());
   handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

   // Annotation-based return value types
   handlers.add(new ModelAttributeMethodProcessor(false));
   //@ ResponseBody Annotation Processor
   handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
         this.contentNegotiationManager, this.requestResponseBodyAdvice));

   // Multi-purpose return value types
   handlers.add(new ViewNameMethodReturnValueHandler());
   handlers.add(new MapMethodProcessor());

   // Custom return value types
   if (getCustomReturnValueHandlers() != null) {
      handlers.addAll(getCustomReturnValueHandlers());
   }

   // Catch-all
   if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
      handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
   }
   else {
      handlers.add(new ModelAttributeMethodProcessor(true));
   }

   return handlers;
}
  • 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
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

As you can see, the RequestResponseBody Method Processor is the @ResponseBody processor.

@ ResponseBody Annotation Processor Call

Enter the invocableMethod. invokeAndHandle (webRequest, mavContainer) / ServletInvocableHandler Method # invokeAndHandle to continue the invocation. Follow the call chain as follows.

  • ServletInvocableHandlerMethod#invokeAndHandle
    • this.returnValueHandlers.handleReturnValue/HandlerMethodReturnValueHandlerComposite#handleReturnValue

The Handler Method ReturnValueHandler Composite # handleReturnValue code is shown below.

public void handleReturnValue(Object returnValue, MethodParameter returnType,
      ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
//Choose an appropriate processor
   HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
   if (handler == null) {
      throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
   }
   //Processing return values
   handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

so, basically, the target processor is selected from all processors and the return value is processed. Enter Handler Method ReturnValueHandler Composite # selectHandler

private HandlerMethodReturnValueHandler selectHandler(Object value, MethodParameter returnType) {
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        //Determine if the processor supports
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

RequestResponseBodyMethodProcessor#supportsReturnType, code as follows.


public boolean supportsReturnType(MethodParameter returnType) {
   return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
         returnType.hasMethodAnnotation(ResponseBody.class));
}
  • 1
  • 2
  • 3
  • 4
  • 5

Obviously, if there is @ResponseBody on the class or method, the processor can be adapted, and @RestController has @ResponseBody semantics that can be adapted, so RequestResponseBodyMethodProcessor#handleReturnValue is used for return value processing.

Topics: Spring REST JSON