A brief review of the author’s previous analysis of HandlerMapping source code, focusing on the analysis of the system structure of the method as the Handler faction. SpringMVC triggers the initialization process of nine strategies through the callback of listeners. In fact, the initialization process of nine strategies is similar. Firstly, determine whether all implementation classes of this policy should be detected in the container. If yes, obtain the objects of these implementation classes and save them into local variables in DispatcherServlet. If no, take the first one. Is by reading the configuration file to obtain the corresponding default policy, which may have multiple for RequestMappingHandlerMapping, is the most frequently in our daily use, for SpringMVC by scanning the entire Spring container for all objects, We then scan these objects, filter only the @Controller or @RequestMapping annotation objects, and iterate over the methods of these objects. Each @requestMapping annotation method is encapsulated as a handler, represented by the generic T. The reason for the use of generic T is not to be explained, but you can understand it if you are interested (because this is a RequestCondition). In our analysis, or in normal cases, we can just treat the generic T as RequestMappingInfo. RequestMappingInfo is an object representation of @requestMapping. A RequestMappingInfo object corresponds to a HandlerMethod, which encapsulates the method annotated by the annotation. Save the Method, bean object references, such a relationship be put in the mappingLookup in attribute mappingRegistry AbstractHandlerMethodMapping object, SpringMVC also stores the URL to RequestMappingInfo relationship in the urlLookup in mappingRegistry, which may be a bit convoluting, but in a nutshell, AbstractHandlerMethodMapping not directly save the url, RequestMappingInfo, HandlerMethod reference relationship, but rather through a MappingRegistry class to save, When the request comes in, it goes to the two maps in this class to find the corresponding HandlerMethod, which is also called handler
HandlerAdapter introduced
So a quick review of the source code implementation of HandlerMapping, we know that when SpringMVC is launched, Url, RequestMappingInfo, HandlerMethod references have been stored in the AbstractHandlerMethodMapping, request to come over, will it take to the corresponding HandlerMethod, Since we're only talking about one of the HandlerMapping types, but there are many HandlerMapping types in SpringMVC, Other HandlerMapping types do not handle requests through HandlerMethod, such as those that directly implement the HttpServlet interface by calling itdoXXX method, DispatcherServlet can unify these invocation methods, for example, when processing requests, no matter what type of handler is obtained, the handle method is called to deal with it, but it is impossible to modify the source code directly. Public Interface HandlerAdapter {Boolean supports(Object handler); public interface HandlerAdapter {Boolean supports(Object handler); ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) long getLastModified(HttpServletRequest request, Object handler); } The first method, passing in an Object handler, is simple enough to determine if the current HandlerAdapter can handle this handler. For HandlerMethod handlers, you just need to check if it's of the HandlerMethod type, and for HttpServlet handlers, you just need to check if it's of the HttpServlet type and the second method, which we mentioned earlier, We need to unify the way all handlers are called. In fact, in SpringMVC, we find a HandlerAdapter corresponding to a handler and then call the Hanle method of the HandlerAdapter to process the request. Therefore, different hanlers only need to provide different adapters. For example, HanlerMethod hanlers only need to call the Invoke Method of the Method object of the HandlerMethod in the handle Method. The others are similar, but not as simple as you might expect for handlerMethodsCopy the code
HandlerAdapter inheritance architecture analysis
As shown in the figure below, there are handlerAdapters5Implementation class, including the most difficult, the most important thing is to AbstractHandlerMethodAdapter this one system, we will focus on the interpretation of the Adapter is how to deal with HandlerMethod, the remaining three implementation class is simpler, We take the HttpRequestHandlerAdapter as example and see how it realize HandlerAdater interface:public class HttpRequestHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
public long getLastModified(HttpServletRequest request, Object handler) {
if (handler instanceof LastModified) {
return ((LastModified) handler).getLastModified(request);
}
return -1L; HttpRequestHandler (HttpRequestHandler);}} HttpRequestHandler (HttpRequestHandler); Then register into the container, at the same time we need to put HttpRequestHandlerAdapter in container, but the adapter is also one of the default policy, so we don't have to put the proper instruments manually in watched the inheritance structure simple, Next we will detailed analysis AbstractHandlerMethodAdapter this factionCopy the code
AbstractHandlerMethodAdapter source
Instead of explaining the main implementation of this class, the Ordered interface and the WebContentGenerator are not very important. The focus is on how the adapter handles HandlerMethod handlerspublic abstract class AbstractHandlerMethodAdapter implements HandlerAdapter {
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception; } analysis: That you can see, the method to realize the HandlerAdapter AbstractHandlerMethodAdapter, supports method is very simple, is to determine whether HttpMethod type. At the same time adds a supportsInternal judgment, however the judgment in subclasses RequestMappingHandlerAdapter is returned directlytrueSo we can assume that when a Handler is of type HandlerMethod, it will be handled by the adapter. Now let's look at the Handle method, which is also very simple to implement. It calls the method handleInternal, A handler is strong to HandlerMethod types with respect to ok, subclass RequestMappingHandlerAdapter to implement this handleInternal, so our focus came outCopy the code
RequestMappingHandlerAdapter overall structure
I. Attribute analysis
- ArgumentResolver
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
privateHandlerMethodArgumentResolverComposite argumentResolvers; When we call the HandlerMethod, we must fetch the Method object stored in it and the bean object, and then use reflection: Method.invoke (bean, args) to complete the call, and it can be thought that when we use it everyday, we may use the following parameters:@RequestMapping( "/test" )
public String test (@RequestBody User user, @RequestParam("name") String name,
HttpServletRequest httpServletRequest, HttpSession httpSession) {
return "index"; } and the parameters of the parser is rev role at this time, the parameters of different parser parsing of the different types of parameters, such as the parser is used to resolve RequestResponseBodyMethodProcessor parameters@RequestBodyIn SpringMVC, before reflection calls HandlerMethod, we'll use the parameter parser to get the corresponding parameters, and then we'll reflect the call, and then we'll analyze the source of those parameters, but the parameter parser is enough to get the corresponding parameters, Let's look at the definition of the parameter parser interface:public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)The supportsParameter method is used to determine whether a parameter can be parsed by the resolveArgument method. The resolveArgument method is used to determine whether the parameter can be parsed by the resolveArgument method. Let's not worry about what these four parameters are...... To talk about the difference between customArgumentResolvers and argumentResolvers, programmers can manually inject different argument parsers, or SpringMVC provides a default argument parser. They will be placed in the customArgumentResolvers at the beginning, when the initialization complete RequestMappingHandlerAdapter, All of the argument parsers in customArgumentResolvers are consolidated into argumentResolvers, ArgumentResolvers is a HandlerMethodArgumentResolverComposite type, Composite is integration/comprehensive meaning, that is easy to understand, SpringMVC uses this class to find and apply the parameter parser, because if you put all the code to find and apply the parameter parser in the HandlerAdapter, it will be coupled, pull all the operations on the parameter parser out into an extra class, HandlerAdapter to hold the class object reference can decouple, is to maintain a HandlerMethodArgumentResolver HandlerMethodArgumentResolverComposite of collectionCopy the code
- ReturnValueHandler
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
privateHandlerMethodReturnValueHandlerComposite returnValueHandlers; This is used to parse the return value of a call to the HandlerMethod. Similarly, there are many types of ReturnValueHandler, such as ours@ResponseBodyRequestResponseBodyMethodProcessor this class is used to complete parsing, this time, you can discover, this kind of like a deja vu!!!!! because@RequestBodyIt is also to parse!! In the meantime, I will not explain the meanings of the above two variables; it is the same case with ArgumentResovlerCopy the code
- HttpMessageConverter
privateList<HttpMessageConverter<? >> messageConverters; Analysis: This messageConverters plays an extremely important role in our SpringMVC. When we process the return value,@ResponseBodyIn the annotation annotation method, the return value is treated as JSON, whereas we return an object, which turns out to be a JSON, which is what this message converter does. For a simple example, if we use the@ResponseBodyWhen you return, you'll use Jackson to convert this object to JSON using ObjectMapper, which you're probably familiar withCopy the code
- SessionAttributes
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
private finalMap<Class<? >, SessionAttributesHandler> sessionAttributesHandlerCache =new ConcurrentHashMap<>(64); Analysis: Let's look at SessionAttributeStore, this class is a utility class, he is to help us put the key - value in the session, it only has a subclass DefaultSessionAttributeStore, This subclass implements the three methods of the SessionAttributeStore interface. The implementation is simple as follows:public class DefaultSessionAttributeStore implements SessionAttributeStore {
public void storeAttribute(WebRequest request, String attributeName, Object attributeValue) {
String storeAttributeName = getAttributeNameInSession(request, attributeName);
request.setAttribute(storeAttributeName, attributeValue, WebRequest.SCOPE_SESSION);
}
public Object retrieveAttribute(WebRequest request, String attributeName) {
String storeAttributeName = getAttributeNameInSession(request, attributeName);
return request.getAttribute(storeAttributeName, WebRequest.SCOPE_SESSION);
}
public void cleanupAttribute(WebRequest request, String attributeName) { String storeAttributeName = getAttributeNameInSession(request, attributeName); request.removeAttribute(storeAttributeName, WebRequest.SCOPE_SESSION); }} Ok, you can see that using storeAttribute as an example, the WebRequest object is used to set the attribute to the session. This method has three parameters: the name of the attribute, the value of the attribute, and the scope. One is the request scope. If the former scope is set to session, then the key-value is set to the request scope. Take a look at sessionAttributesHandlerCache this Map, the key for the Class object, the value of SessionAttributesHandler, SessionAttributesHandler is@SessionAttributesThe object representation of this annotation, as you can see from the previous article,@SessionAttributesThe scope is class, so the map represents the name of the class to the location of the class@SessionAttributesThe mapping ofpublic class SessionAttributesHandler {
private final Set<String> attributeNames = new HashSet<>();
private finalSet<Class<? >> attributeTypes =new HashSet<>();
private final Set<String> knownAttributeNames = Collections.newSetFromMap(new ConcurrentHashMap<>(4));
private finalSessionAttributeStore sessionAttributeStore; } We can do it in@SessionAttributesIn the annotation, name and type are configured to correspond to attributeNames and attributeTypes in SessionAttributesHandler. In order to unify the two, name can be used to search directly. SpringMVC introduced knownAttributeNames, because when we configure type, when a request ends, the Model object in the request, if it's one of these Type attributes, So the key-value in the Model is going to be put into the session, and at the same time, in order to get it directly from the session with name, and save it in the knownAttributeNames, That's integrating attributeNames and attributeTypes, and SessionAttributesHandler has SessionAttributeStore built into it for a very simple reason, The SessionAttributeStore object is used to set key-value to and from the session, so the SessionAttributeStore object can operate directly in the SessionAttributesHandler. This is in RequestMappingHandlerAdapter SessionAttributeStore really is created, because as long as there is a copy of the tool is ok, just hold the tool as part of a SessionAttributesHandler. Passed in through the constructorCopy the code
- InitBinder && ModelAttribute
private finalMap<Class<? >, Set<Method>> initBinderCache =new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache
= new LinkedHashMap<>();
private finalMap<Class<? >, Set<Method>> modelAttributeCache =new ConcurrentHashMap<>(64);
private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache
= new LinkedHashMap<>();
privateHandlerMethodArgumentResolverComposite initBinderArgumentResolvers; Analysis: After the analysis of the previous article, we have a clear understanding@InitBinderand@ModelAttributeInitBinderCache and modelAttributeCache, respectively. Key is the Controller object. A Controller allows multiple annotation methods, but what if they are global? namely@ControllerAdviceIn, will be in to initBinderAdviceCache and modelAttributeAdviceCache respectively Because we call@InitBinderLabeling method, can be incoming parameters, parameter analysis is to use initBinderArgumentResolvers, can see its type is HandlerMethodArgumentResolverComposite, That is, multiple argument parsers will be put into this class, but our normal handler arguments will be resolved using the argumentsResolvers mentioned aboveCopy the code
Second, constructor analysis
To explain in detail the properties related to talk about when the constructor, as well as RequestMappingHandlerAdapter source analysis, the first step in using the object must first create the object:public RequestMappingHandlerAdapter(a) {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
this.messageConverters.add(new SourceHttpMessageConverter<>());
this.messageConverters.add(newAllEncompassingFormHttpMessageConverter()); } As you can see, the constructor creates a series of HttpMessageconverters. Here is an example that we use everyday@ResponseBodySpeaking:@Controller
public class TestController1 {
@GetMapping( "/request1" )
@ResponseBody
public List<String> request1 (a) {
return Arrays.asList( "A"."B"); }} analysis: in the request /request1, by@ResponseBodyMark before the return values are what we call ReturnValueHandler processing, really are processed by RequestResponseBodyMethodProcessor, first turn JSON, For SpringMVC with Jackson package provides the function of serializing JSON, so we're going to introduce this Jackson bag, otherwise it is request execution of complains, secondly, in this summary on RequestMappingHandlerAdapter constructor, I'm going to put in a class that serializes JSON with this Jackson, so let's seethis.messageConverters.add(newAllEncompassingFormHttpMessageConverter()); Let's look at AllEncompassingFormHttpMessageConverter part in this class:public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
AllEncompassingFormHttpMessageConverter.class.getClassLoader()) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
AllEncompassingFormHttpMessageConverter.class.getClassLoader());
public AllEncompassingFormHttpMessageConverter(a) {
if (jackson2Present) {
addPartConverter(newMappingJackson2HttpMessageConverter()); }}} classutils. isPresent is a simple methodtry.catch.tryIf the reflection fails to create the string, the Class does not existcatchWhen the class is initialized, the ObjectMapper class is checked to see if it existstrue. Then in the constructor will save the MappingJackson2HttpMessageConverter news converter into within partConverters attributes And held a ObjectMapper object in MappingJackson2HttpMessageConverter, determine whether a message converter is suitable for the current, the return value is the judgment above the request examples were analyzed, and the return is a List < String >. When returned, if the url is requested with the corresponding mediaType, for example"application/json", the message converter will be used to handle the return value, which I was previously cheated on here.... Send it directly with a browser GET request and find that the return value is an XML..... Because must determine what use which message converter by traversing the way of judgment, because the time is not clearly specified request was the content-type, so haven't traversal to MappingJackson2HttpMessageConverter has find the converterCopy the code
AfterPropertiesSet is further initialized
public void afterPropertiesSet(a) {
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = newHandlerMethodReturnValueHandlerComposite().addHandlers(handlers); }} analysis: initialization is very simple, it is the first step initControllerAdviceCache initializing global ControllerAdvice defined@ModelAttributeAs well as@InitBinderTo find these global methods into the corresponding cache, the previous has been detailed analysis of the relevant cache, at the same time to find the@ControllerAdviceAnnotated classes that implement either the RequestBodyAdvice or ResponseBodyAdvice interface, place them in the corresponding property. The RequestBodyAdvice or ResponseBodyAdvice interface, which I'm not going to expand here, is just an extended processing of the data that's returned from the request and then in three steps, Access to all of the default ArgumentResolvers, InitBinderArgumentResolvers and ReturnValueHandlers, And then into the compound type of HandlerMethodArgumentResolverComposite, is also very simple, is straight throughnewThe corresponding components, we can use getDefaultArgumentResolvers part of code to demonstrate:private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers(a) {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.requestResponseBodyAdvice));
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
if(getCustomArgumentResolvers() ! =null) {
resolvers.addAll(getCustomArgumentResolvers());
}
returnresolvers; } as you can see, is directnewHowever, if the manual supplied by the programmer is not empty, it will be put into it, and finally returnedCopy the code
RequestMappingHandlerAdapter processing HandlerMethod
HandleInternal method entry
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
mav = invokeHandlerMethod(request, response, handlerMethod);
if(! response.containsHeader(HEADER_CACHE_CONTROL)) {if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else{ prepareResponse(response); }}returnmav; } : in this method, I delete the judgment of session synchronization, here you don't need to worry about, there is no significance..... So to make this code easy to understand, we did this, and you can see that the whole handleInternal method does just three things, and it returns a ModelAndView, and ModelAndView, notice, means a combination of Model and View, Combined with the analysis of the last article, we know that the Model object is a Map that runs through the whole process of executing the request, and the view is the view that we return to the front end after executing the request. The checkRequest method, which we'll examine in more detail in the next section, does some checking to see if the current request is supported. In general, all requests are supported. What are we saying@GetMappingAs well as@PostMappingThat's not what we're doing here, because we're in the outer layer, we're not dealing with the HandlerMethod yet, and then we're dealing with the session, and if the session is required, But if there's no session in the current request it throws an exception and then the core method invokeHandlerMethod, which passes in HttpServletRequest, HttpServletResponse, And HandlerMethod. We will analyze the process in detail later. Finally, we will handle the request Cache. If there is no response header cache-control in the response, some processing will be done If you're interested in going in there, there might be a judgment about SessionAttributeHandler, and if you look at the analysis of invokeHandlerMethod, If you look at the SessionAttributeHandler judgment, it's a lot clearerCopy the code
invokeHandlerMethod
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers ! =null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers ! =null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
invocableMethod.invokeAndHandle(webRequest, mavContainer);
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally{ webRequest.requestCompleted(); }} analysis: First of all, I have removed all the code related to asynchronous requests, which I am not familiar with and have not used before, and the whole purpose of my article is to analyze the execution flow of SpringMVC in general. We passed a WebRequest object to the Adapter SessionAttributes attribute. Use the setAttribute of this object and provide a scope to decide whether to set key-value to request or session, and the WebRequest object is created here, subclass ServletWebRequest...... Put request and Response into a ServletWebRequest, and then you can use this object to get information about request and response. And set key-value to request or session and then we're going to look at the components that are involved in this code one by one, and I'm sure it'll sound pretty smooth with the analysis of Adapter properties and components in this sectionCopy the code
WebDataBinderFactory
The name of this component can be understood as two parts, WebDataBinder and Factory, which, as the name implies, is the WebDataBinder object Factory, so the core is the WebDataBinder, which was analyzed earlier@InitBinderYou can initialize the WebDataBinder while@InitBinderThere are two forms, one that only works on the current Controller, and one that is placed on@ControllerAdviceGlobal on the annotated classprivate WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { Class<? > handlerType = handlerMethod.getBeanType(); Set<Method> methods =this.initBinderCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first
this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for(Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); }}});for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
returncreateDataBinderFactory(initBinderMethods); } initBinderCache is a cache that fetchall the files in the Controller class of the current HanlderMethod@InitBinderMethodIntrospector is used to scan HandlerMapping. MethodIntrospector is used to scan HandlerMapping. Find it and put it in the cache so the first part is to find everything in the current Controller@InitBinderIt's just a method called, and then it initializes a List that holds the InvocableHandlerMethod, So we're going to look at what the InvocableHandlerMethod is and the second part of the code is going to iterate through this global cache, initBinderAdviceCache, because when the Adapter is initialized, it's going to have all of the elements in the container that are called, okay@ControllerAdviceThe labeled class is found, and the global@InitBinderThe annotated method is stored in initBinderAdviceCache, and it is then necessary to retrieve from this cache the method that can be used in the current Controller, as I mentioned earlier,@ControllerAdviceYou can configure which packages and classes are applied to, and this step is to filter out these methods. The third part is to iterate through all the methods in the current Controller and turn them into invocableHandlerMethods. And put it in the initBinderMethods collection and finally, initBinderMethods is the collection that holds the current Controller and the ones that meet the criteria@ControllerAdviceAll of the annotated classes@InitBinderAnnotation methods, and these methods are wrapped in the InvocableHandlerMethodCopy the code
InvocableHandlerMethod
It's a callable HandlerMethod, that's right, this class inherits HandlerMethod, why is it callable? HandlerMethod encapsulates the bean object, Method object and parameter information, but cannot be called directly, because there is no parameter value, and the return value after the call is different. This is what the InvocableHandlerMethod needs to do. The InvocableHandlerMethod inherits the HandlerMethod and provides several attributes, as can be seen from the following class definition:public class InvocableHandlerMethod extends HandlerMethod {
private WebDataBinderFactory dataBinderFactory;
private HandlerMethodArgumentResolverComposite argumentResolvers
= new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer
= newDefaultParameterNameDiscoverer(); } as you can see, it saves the parameter parser, WebDataBinderFactor, and a parameter name finder, which we won't go into too much depth..... InvocableHandlerMethod has a direct subclasses: ServletInvocableHandlerMethod, the direct subclass adds an additional attributespublic class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
privateHandlerMethodReturnValueHandlerComposite returnValueHandlers; } Now, the role of the InvocableHandlerMethod is obvious. Add the parameter value, return value processing to the HandlerMethod, and then go back to the previous paragraph to add these@InitBinderCall createDataBinderFactory with the set of InvocableHandlerMethods as arguments. In fact, it isnewA ServletRequestDataBinderFactory, it is important to note that in here is InvocableHandlerMethod class object, rather than ServletInvocableHandlerMethod, investigate its reason is@InitBinderReturn values are not allowed. Try..... So there is no need to use ServletInvocableHandlerMethod at allCopy the code
Summary
ServletWebRequest stores HttpServletRequest and HttpServletResponse, and also provides the ability to put key/value pairs into session or Request scope. WebDataBinderFactory takes all the @initBinder methods out of the WebDataBinder factory method and puts them into that factory method, Do all @initBinder methods initialize the WebDataBinder after it is created on each request, such as adding a custom property editor?Copy the code
ModelFactory
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); The next thing we need to analyze is the ModelFactory, which has been processed before@InitBinderAnnotation, put them in the WebInitBinderFactory, which is used to process them@ModelAttributeAnnotated, because as we said before, every request is executed as it is executed@InitBinderAs well as@ModelAttributeAnnotation annotation methodprivate ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) { SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); Class<? > handlerType = handlerMethod.getBeanType(); Set<Method> methods =this.modelAttributeCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
// Global methods first
this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for(Method method : methodSet) { attrMethods.add(createModelAttributeMethod(binderFactory, bean, method)); }}});for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
return newModelFactory(attrMethods, binderFactory, sessionAttrHandler); } analysis: code structure you should feel familiar with, with@InitBinderIs handled globally first, then locally. Finally, each scanned method is wrapped as an InvocableHandlerMethod and given to the parameter parser, This step is done in the middle of createModelAttributeMethod method Finally, generates a ModelFactory object, in this construction method, will InvocableHandlerMethod and carried out a package, It becomes ModelMethod, and the result of wrapping is for processing@ModelAttributeThe annotation method also has parameters@ModelAttributeNote solution of the situation, we are interested in going in to see it, very simple.... Finally, it is important to note that in the way of the opening getSessionAttributesHandler calling a method, at the same time will return back to the value of this method in the ModelFactory, first say next SessionAttributesHandler, I've been talking about this component, and what it does is@SessionAttributesThe class representation of knownAttributeNames has four parameters, one is attributeNames, which corresponds to the name in the annotation, and the other is attributeTypes, which corresponds to the Type in the annotation. KnownAttributeNames combines the two parameters. The last parameter is SessionAttributeStore, which provides a key-value operation on the session, and we know that SessionAttributesHandler is class-scoped. Every class has one of these objects. This is what getSessionAttributesHandler methodprivate SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) { Class<? > handlerType = handlerMethod.getBeanType(); SessionAttributesHandler sessionAttrHandler =this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrHandler == null) {
synchronized (this.sessionAttributesHandlerCache) {
sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrHandler == null) {
sessionAttrHandler
= new SessionAttributesHandler(handlerType, sessionAttributeStore);
this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler); }}}returnsessionAttrHandler; } This handler method is used to retrieve the type of the Controller in which it is located from the cachesynchronizedSessionAttributeStore is just a tool for manipulating key-values in a session. So it doesn't matter if you upload this piece directly, just share the same tool... Finally, the created SessionAttributesHandler is put in the cache, so even if a class is not@SessionAttributesThe annotation also creates a SessionAttributesHandler object, except that the three collections of those attributes are empty collections that have just been initializedCopy the code
ServletInvocableHandlerMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers ! =null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers ! =null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); The next is to create ServletInvocableHandlerMethod object, this class is a subclass of InvocableHandlerMethod, under the function of its parent class provides a return value of the parser, namely returnValueHandlers, So it's adapted to handle real handler methods, and you can see that by setting the compound parameter parser (which has all the initialized parameter parsers in it) and the compound return value parser (which has all the initialized return value parsers in it) into the invocableMethod, Now, if you call handler, that's a good way to parse the parameters and the return value, but it's not enough, we also need a WebDataBinder for the parameter binding, where the data comes from and we'll talk about that later, we have to have a WebDataBinder for the parameter binding, So the invocableMethod puts in the WebDataBinderFactory that we created earlier@InitBinderAnnotations)Copy the code
ModelAndViewContainer
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); Analysis: From the previous operations, we have successfully added the WebDataBinder,@ModelAttributeAnd the handler that needs to be called, but what's missing is the data, and that data goes into the ModelAndViewContainer, and that container holds all the data that needs to be bound, And the name of the view that is returned after the Hanlder method is executed. We're not going to analyze what this InputFlashMap does, we're going to run into an OutputFlashMap, we're going to ignore that, we're going to focus on that component, It's used for redirecting requests like this so the most important thing in these lines of code is initModel, The parameters are a webRequest(the key defined by the current class's SessionAttributesHandler from the session), and a mavContainer(to store all the data).public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception { Map<String, ? > sessionAttributes =this.sessionAttributesHandler.retrieveAttributes(request);
container.mergeAttributes(sessionAttributes);
invokeModelAttributeMethods(request, container);
for (String name : findSessionAttributeArguments(handlerMethod)) {
if(! container.containsAttribute(name)) { Object value =this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name); } container.addAttribute(name, value); }}} analysis: At this time has arrived in ModelFactory, combined with the previous analysis, we know that has the corresponding sessionAttributesHandler ModelFactory, So now you call the retrieveAttributes method of that sessionAttributesHandler directly, and the result is using the knownAttributeNames in that sessionAttributesHandler, Find their corresponding values in the session and return them as a Map and we said that ModelAndViewContainer has a function to hold parameters that can be bound to, and you can see that here, Container. MergeAttributes sessionAttributes the map information is in fact the call transferred to the corresponding Model in the container, is actually a map, inside the code is very simple, do not stick out, If you're interested in a container, in addition to storing session information, you should also store it@ModelAttributeModified method returns the key - value, and the call is complete the function of invokeModelAttributeMethods method, in this method, first remove the into before we all are in ModelFactory@ModelAttributeThe annotated method is put into the modelMethod property in the ModelFactory, and then iterated one by one, calling the method at the same time, retrieving the return value, and finally generating the key using certain name generation rules (if the annotation has a name, the name is used as the key). InitModel initializes the Model object (which is a map) in ModelAndViewContainer. The sum of session data that will satisfy the condition@ModelAttributeThe data is put into the Model.... Next up is aforThe role of circulation, findSessionAttributeArguments method is to find the current in all the parameters in the handler@ModelAttributeThe argument to the annotation, which is also defined in sessionAttributesHandler, for example:@Controller
@SessionAttributes( value = "customer")
public class TestController1 {
@RequestMapping( value="/request1" )
@ResponseBody
public Customer request1 (@ModelAttribute( "customer" ) Customer customer) {
return newCustomer(); }} For example, the customer key is present in the request1 handler@ModelAttributeThe parameters of the annotation also exist in@SessionAttributesIf the container does not contain the data, it does not find the data after initialization. In this case, an exception is raised because the request1 method needs to bind the attribute corresponding to the customer key, but it does not find the attribute, and an exception is raised....Copy the code
Summary
WebDataBinderFactory: WebDataBinderFactory: WebDataBinderFactory: WebDataBinderFactory: Holds @initBinder information about ModelFactory: Holds @modelAttribute and @sessionAttributes. The former takes the form of invocableHandlerMethods wrapped as ModelMethod in the ModelFactory object. The latter form is SessionAttributesHandler ServletInvocableHandlerMethod: The object that is used to trigger the call to the handler method stores the ModelAndViewContainer that needs the parameter parser, return value parser, and so on: Calling the handler method involves binding parameters that are put into the Container from @ModelAttribute and @sessionAttributes, or ModelFactory. The trigger is the initModel method. The data is actually stored in the Container defaultModel, which is a Map. The Container also stores the view information after the handler method is calledCopy the code
Start calling HandlerMethod
- InvokeAndHandle is the call entry for HandlerMethod
InvocableMethod. InvokeAndHandle (webRequest mavContainer) : this method is the entrance, then let's take a look at its internal implementation!!!!!!public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null) {
if(isRequestNotModified(webRequest) || getResponseStatus() ! =null
|| mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return; }}else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } Analysis: Let's start with the requestHandled property in mavContainer.trueIndicates that the request is completed.falseIn fact, the most core code in the whole method is two lines, one is to call the HandlerMethod method, one is to handle the return value, we will focus on these two methodsCopy the code
- InvokeForRequest starts calling HandlerMethod
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
Object returnValue = doInvoke(args);
returnreturnValue; } analysis: As you can see, we first get the parameter, then we use this parameter and the bean stored in the HandlerMethod to reflect the method directly. In doInvoke, we call getMethodArgumentValues to reflect the parameter. Not up into the ServletInvocableHandlerMethod to like before we put a series of parameters of the parser, this time is to traverse the parser and traverse these parameters, the first step to find the current traversal parameters corresponding to the parser, for example@RequestBodyIs to use RequestResponseBodyMethodProcessor the parser to parse, judge whether a parameter parser is suitable for the current parameters, Using parameter parser universal interface HandlerMethodArgumentResolver supportsParameter method, and analytical parameters using resolveArgument method, Than such as RequestResponseBodyMethodProcessor parameter parser is such a judgment:public boolean supportsParameter(MethodParameter parameter) {
returnparameter.hasParameterAnnotation(RequestBody.class); } This class uses Jackson's ObjectMapper as an argument to get the argument from the resolveArgument method. He will get a MessageConverter message converter, actually using MappingJackson2HttpMessageConverter this converter, which is using ObjectMapper deserializationCopy the code
- HandleReturnValue handles the return value
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); } handleReturnValue is similar to handling parameters, handling parameters is to find the appropriate parameter parser, handling the return value will find the appropriate value parser, while handling the return value@ResponseBodyIn terms of its return value parser@RequestBodyThe parameter resolver is the same.... One for converting JSON to objects and one for converting objects to JSONCopy the code
conclusion
In the above analysis, we first through the previous two articles introduced some related knowledge points, in the case of understanding these knowledge points to look at the source will not have a blind spot, then we analyzed the HandlerAdapter system, Led to the development of the most popular @ RequestMapping corresponding adapter RequestMappingHandlerAdapter, first analyzes the attributes of the object adapter, indicate the function of each attribute, Then the initialization of the object in the Spring container is analyzed. During the initialization process, various components are initialized, such as message converter, parameter parser, return value parser, initBinder, modelAttribute and so on. After understanding the whole, Let's dive into the handleInternal method, where we call the HandlerMethod, Which involves WebDataBinderFactory, ModelFactory, ServletInvocableHandlerMethod, ModelAndViewContainer four components, and has carried on the detailed analysis, Finally, we took a look at the parameter parser and return value parser processing, so far, as the most complex HandlerAdapter component in SpringMVC analysis completed....Copy the code