Every sentence
One fact is that for most technologies, it takes only one day to understand, and a week to make it simple. It may only take a month to get started.
Preface
adopt The first two articles I believe you have a deep understanding of Handler Method Argument Resolver. But you may have the same feeling as me. What seems to be the shortcomings?
How do we encapsulate requests with @RequestBody, which we use very frequently? This is a very widely used area, but there is no explanation, because it handles in a different way from the previous one, so just pick it out to describe in detail in this article.
Category 4: Message Converter Type Based on ContentType
Using HttpMessageConverter to convert input stream into corresponding parameters
The base class of such parameter parsers is AbstractMessageConverter Method ArgumentResolver:
// @since 3.1 public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { // Default supported method (no Deleted method) // httpMethod is null or method does not belong to this set or does not have contendType and body, then return null // That is to say, if it is a Deleted request, even if there is a value in the body, it returns null. (Because it is not SUPPORTED_METHODS) private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH); private static final Object NO_VALUE = new Object(); protected final List<HttpMessageConverter<?>> messageConverters; protected final List<MediaType> allSupportedMediaTypes; // Related to Request Body Advice and Response Body Advice private final RequestResponseBodyAdviceChain advice; // Specify HttpMessageConverter in the constructor // The constructor of this parameter is called by Muren public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) { this(converters, null); } // @since 4.2 public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) { Assert.notEmpty(converters, "'messageConverters' must not be empty"); this.messageConverters = converters; // It brings together all the MediaType s that are supported in all message converters.~ this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters); this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice); } // Provide a defualt method access RequestResponseBodyAdviceChain getAdvice() { return this.advice; } // Subclass RequestResponseBodyMethodProcessor has a copy of this method @Nullable protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpInputMessage inputMessage = createInputMessage(webRequest); return readWithMessageConverters(inputMessage, parameter, paramType); } ... }
Note: This abstract class does not implement the resolveArgument() interface method, but only provides some protected methods as tool methods to call subclasses. For example, the most important method: readWithMessageConverters() is to parse the core of HttpInputMessage using message converters.
For a description of this abstract class, you can see Here, the HttpMessageConverter matching rule
Its succession tree is as follows:
RequestPartMethodArgumentResolver
It is used to parse parameters that are modified by @RequestPart, or whose type is javax.servlet.http.Part provided by MultipartFile | Servlet 3.0 (and not modified by @RequestParam), and whose data is obtained by HttpServletRequest.
When the attribute is marked @RequestPart, it is parsed by HttpMessageConverter combined with Content-Type, which is particularly similar to the way @RequestBody handles it.~
// @since 3.1 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestPart { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; }
// @since 3.1 public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver { // Annotated with the @RequestPart annotation // There is no @RequestPart tag and no @RequestParam tag, but a Multipart type also handles it. @Override public boolean supportsParameter(MethodParameter parameter) { if (parameter.hasParameterAnnotation(RequestPart.class)) { return true; } else { if (parameter.hasParameterAnnotation(RequestParam.class)) { return false; } return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional()); } } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class); boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional()); // If the comment is not specified, take the formal parameter name String name = getPartName(parameter, requestPart); parameter = parameter.nestedIfOptional(); Object arg = null; // The resolveMultipartArgument method only deals with: // MultipartFile type and corresponding array/collection type // Part type and corresponding array collection type // If the parameter type is not the above type, return UNRESOLVABLE (empty object) // Finally return to Standard MultipartHttpServletRequest/request.getParts() [0], etc.~ Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest); if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) { arg = mpArg; // It's part type, so assign it directly. } else { // Other types ... } ... } }
This processor is used to parse the @RequestPart parameter type, which is related to most file uploads. With regard to file uploading in Spring MVC, it is not convenient to expand here. There is a special topic on uploading and downloading in Spring MVC.~
AbstractMessageConverter Method Processor
Naming Processor indicates that it can handle both input and return values. Of course, the focus of this article is method input (related to HttpMessageConverter).
The body of the request is usually a string/byte stream, and the query parameters can be seen as part of the URL, which are located in different parts of the request message.
Form parameters can be placed in the body of the request in a certain format or on the url as query parameters.
Response body is the specific content returned by response. For a normal html page, body is the source code of the page. For the HttpMessage response body, it may be a json string (but not mandatory).
Response bodies are typically used in conjunction with Content-Type, telling clients that they only know how to render this header.
AbstractMessageConverter Method Processor source code is slightly complex, and it is closely related to Http protocol and content negotiation.
// @since 3.1 public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler { // By default: Files are suffixed with these no window Downloads private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<>(Arrays.asList("txt", "text", "yml", "properties", "csv", "json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp")); private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<>(Arrays.asList("audio", "image", "video")); private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = Arrays.asList(MediaType.ALL, new MediaType("application")); private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference<List<ResourceRegion>>() { }.getType(); // Used to decode UrlPathHelper. decodeRequestString (servlet Request, filename) for URL s; private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper(); // rawUrlPathHelper.getOriginatingRequestUri(servletRequest); private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper(); static { rawUrlPathHelper.setRemoveSemicolonContent(false); rawUrlPathHelper.setUrlDecode(false); } // Content Negotiation Manager private final ContentNegotiationManager contentNegotiationManager; // Content Negotiation Strategy for Extensions private final PathExtensionContentNegotiationStrategy pathStrategy; private final Set<String> safeExtensions = new HashSet<>(); protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) { this(converters, null, null); } // ContentNegotiation Manager can be specified protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager contentNegotiationManager) { this(converters, contentNegotiationManager, null); } // This constructor is the point. protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) { super(converters, requestResponseBodyAdvice); // As you can see, by default, a new one will be created directly. this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager()); // If there is one in the manager, use the one in the manager. Otherwise, the new Path Extension Content Negotiation Strategy () this.pathStrategy = initPathStrategy(this.contentNegotiationManager); // Install all suffixes supported by content negotiation with safe Extensions // And add the suffix whitelist (which means the suffix is supported by default) this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions()); this.safeExtensions.addAll(WHITELISTED_EXTENSIONS); } // Servlet Server HttpResponse is the packaging of HttpServlet Response, which mainly deals with the response head. // Mainly deal with: setContentType, setCharacterEncoding, etc. // So if a subclass wants to write data, it calls this method to write to the output stream.~~~ protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) { HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Assert.state(response != null, "No HttpServletResponse"); return new ServletServerHttpResponse(response); } // Note: The createInputMessage() method is provided by the parent class, wrapping HttpServletRequest // The main processing methods are getURI(), getHeaders(). // The getHeaders() method deals mainly with: getContentType()... protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); writeWithMessageConverters(value, returnType, inputMessage, outputMessage); } // This method is omitted // This method is the core of message processing: dealing with contentType, message transformation, content negotiation, download, and so on. // Note: The RequestResponseBodyAdviceChain is also executed here for forward and backward interception. protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... } }
The core of this class is a variety of HttpMessageConverter message converters, because the final write s are left to them to complete.
In this abstract class, it completes content negotiation~
For details of content negotiation, it is strongly recommended that you click Here . in addition This article AbstractMessageConverter MethodProcessor is also analyzed in depth, which can be used as a reference.
Since the parent class has done so much, the subclass is naturally very simple. Look at its two concrete implementation subclasses:
RequestResponseBodyMethodProcessor
As the name implies, it handles the @RequestBody annotation parameter
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); } @Override public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); // So the core logic: read flow, message exchange and so on are all completed in the parent class. The converted value arg can be obtained by calling the subclass directly. // arg is generally a class object. For example, the Person instance Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); // If POJO, it's lowercase (not formal) for the class name. String name = Conventions.getVariableNameForParameter(parameter); // Data validation (which has been analyzed in detail before, as mentioned here) if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } // Put the verification results into the Model for easy access on the page if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } // Adaptation: Parameters supported to Optional type return adaptArgumentIfNecessary(arg, parameter); } }
HttpEntityMethodProcessor
Used to handle HttpEntity and RequestEntity type entry.
public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { return (HttpEntity.class == parameter.getParameterType() || RequestEntity.class == parameter.getParameterType()); } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException { ServletServerHttpRequest inputMessage = createInputMessage(webRequest); // Get the generic type of HttpEntity Type paramType = getHttpEntityType(parameter); if (paramType == null) { // Note: This generic type must be specified and required throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() + "' in method " + parameter.getMethod() + " is not parameterized"); } // Call the parent method to get the value of body (pass in the generic type, so return an instance) Object body = readWithMessageConverters(webRequest, parameter, paramType); // Note step operation: new a Request Entity goes in and holds the instance. if (RequestEntity.class == parameter.getParameterType()) { return new RequestEntity<>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI()); } else { // If you use the parent HttpEntity, you will lose information such as Method (so it is recommended to refer to the RequestEntity type, which is more powerful). return new HttpEntity<>(body, inputMessage.getHeaders()); } } }
Note: There is no validate check here, which is often asked by interviewers: What is the difference between using HttpEntity and @RequestBody?
From the code you can see intuitively: with the abstract parent class, the subclass needs to do very little, just match the parameter type, do different returns.
There is no need to show their use cases here, because you are familiar with them in your daily work. But for their use, I summarize the following details for reference:
- @ RequestBody/HttpEntity whose parameter (generic) type is allowed to be Map
- Both @ResponseBody on methods and classes can be inherited, but @RequestBody cannot.
- @ RequestBody comes with Bean Validation checking capabilities (which of course need to be enabled), and HttpEntity is lighter and more convenient.
The package of HttpEntity/RequestEntity is: org.spring framework.http, which belongs to spring-web.
@ RequestBody is located at org. spring framework. web. bind. annotation and belongs to spring-web as well.
Finally, an Errors Method ArgumentResolver was dropped, which is added here:
ErrorsMethodArgumentResolver
It is used to write Errors type in method parameters to get data validation results.
public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return Errors.class.isAssignableFrom(paramType); } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { Assert.state(mavContainer != null, "Errors/BindingResult argument only supported on regular handler methods"); ModelMap model = mavContainer.getModel(); String lastKey = CollectionUtils.lastElement(model.keySet()); // Only the @RequestBody/@RequestPart annotation has value in it. if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) { return model.get(lastKey); } // Simply put: The Errors parameter is meaningful only if you have the annotation @RequestBody/@RequestPart throw new IllegalStateException( "An Errors/BindingResult argument is expected to be declared immediately after " + "the model attribute, the @RequestBody or the @RequestPart arguments " + "to which they apply: " + parameter.getMethod()); } }
Registration and Order of Spring MVC Parametric Processors
So far, ArgumentResolver, a built-in parameter processor for Spring MVC, has said it all over again.
As I mentioned earlier, parameter processing is sensitive to the order of processors, so we need to focus on the final execution order of Spring MVC, when our aggregation container Handler Method ArgumentResolver Composite comes out:
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver { private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>(); // Having a cache private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256); ... // @ since 4.3 Wood Called anywhere public void clear() { this.argumentResolvers.clear(); } // getArgumentResolver() method is the core of this paper @Override public boolean supportsParameter(MethodParameter parameter) { return getArgumentResolver(parameter) != null; } @Override @Nullable public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { // Here's the key: each parameter can be processed by at most one processor HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter); if (resolver == null) { throw new IllegalArgumentException("Unsupported parameter type [" + parameter.getParameterType().getName() + "]." + " supportsParameter should be called first."); } return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); } ... // This logic ensures that each parameter can be processed by only one processor at most. // This can also be seen from the cached data structure @Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) { if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; } }
By default, Spring MVC registered processors (in order) are as follows:
The code at its initialization is as follows:
RequestMappingHandlerAdapter: @Override public void afterPropertiesSet() { ... // 26, see method getDefault ArgumentResolvers for details if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } // For details, see getDefaultInitBinderArgumentResolvers if (this.initBinderArgumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } ... }
Note: In this case, initBinderArgumentResolvers will end up with only 12 processors, because its registration method is as follows (in this order):
As mentioned earlier, many types of parameters can be written in the @InitBInder annotation, but because it only has 12 processors, some parameters can not be written (such as @RequestBody, Errors and so on) without enumerating them one by one, so that you have a good idea.
summary
In fact, the processing content introduced in this paper is more important, because it is related to the message converter HttpMessageConverter. After all, it is the mainstream way of our current use, and I hope it can help you understand.
Here's a warning: The next article will be very important, because I'll demonstrate a solution to a special scenario that we implemented through a custom Handler Method Argument Resolver, which is very elegant and worthy of promotion. Interested people will continue to pay attention to it.
Relevant Reading
Handler Method ArgumentResolver (1): Controller method into parameter auto-encapsulator (parse parameter into value) [Enjoy learning Spring MVC]
Handler Method ArgumentResolver (II): Map parameter type and fixed parameter type [Enjoy Spring MVC]
Handler Method ArgumentResolver (3): Parametric Processor Based on HttpMessageConverter Message Converter
Knowledge exchange
== The last: If you think this is helpful to you, you might as well give a compliment. Of course, sharing your circle of friends so that more small partners can see it is also authorized by the author himself.~==
** If you are interested in technical content, you can join the wx group: Java Senior Engineer and Architect.
If the group two-dimensional code fails, Please add wx number: fsx641385712 (or scan the wx two-dimensional code below). And note: "java into the group" will be manually invited to join the group**
== If you are interested in Spring, Spring Boot, MyBatis and other source code analysis, you can add me wx: fsx641385712, invite you to join the group and fly together manually.==