HandlerAdapter of Spring MVC component
HandlerAdapter overview
The HandlerAdapter component is an adapter of a processor Handler. The main function of the HandlerAdapter component is to adapt a specific Handler to handle the corresponding request.
In the source code of spring MVC, HandlerAdapter is an interface. This interface mainly defines three methods.
1.boolean supports(Object handler)
Determine whether the handler adapter component supports this handler instance.
2.ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
The handler adapter component uses the handler instance to process specific requests.
3.long getLastModified(HttpServletRequest request, Object handler)
Get the last modified value of the resource. This method has been abandoned.
HandlerAdapter class diagram
As can be seen from the above class diagram, the inheritance structure of the HandlerAdapter interface family is relatively simple. The four classes HandlerFunctionAdapter, HttpRequestHandlerAdapter, SimpleControllerHandlerAdapter and SimpleServletHandlerAdapter directly inherit the HandlerAdapter interface. Only the RequestMappingHandlerAdapter class is slightly more complex. The RequestMappingHandlerAdapter class indirectly inherits the HandlerAdapter interface. The RequestMappingHandlerAdapter class first inherits the abstract class AbstractHandlerMethodAdapter, and the abstract class AbstractHandlerMethodAdapter inherits the HandlerAdapter interface.
RequestMappingHandlerAdapter
AbstractHandlerMethodAdapter
AbstractHandlerMethodAdapter is an abstract class that inherits the HandlerAdapter interface and implements the three methods of the HandlerAdapter interface, namely, supports, handle and getLastModified.
The getLastModified method has been deprecated and will not be described.
supports method
@Override public final boolean supports(Object handler) { return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); }
The supports method is mainly used to determine whether the adapter supports this handler. The logic in the code mainly determines whether the handler is an instance of the HandlerMethod class, and then synthesizes the return value of the supportsInternal method.
The supportsInternal method is only a virtual method in the AbstractHandlerMethodAdapter class. It is mainly provided to the subclass RequestMappingHandlerAdapter for concrete implementation.
@Override @Nullable public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); }
In the handle method, the handleInternal method is directly called. The handleInternal method is only a declaration of a virtual method in the AbstractHandlerMethodAdapter class, which is specifically provided to subclasses for implementation. The handleInternal virtual method is finally given a specific logical implementation in the RequestMappingHandlerAdapter class.
AbstractHandlerMethodAdapter implements the Ordered interface. This interface is mainly used for sorting.
AbstractHandlerMethodAdapter also inherits the WebContentGenerator class, which is a superclass of the web content generator. It provides some properties and methods such as browser cache control, whether session must be opened, and supported request method types (GET, POST, etc.).
There is a checkRequest method in the WebContentGenerator, which is mainly used to detect requests. This method will be called in the subclass RequestMappingHandlerAdapter.
RequestMappingHandlerAdapter
When the Spring MVC container initializes a component of HandlerAdapter type, the RequestMappingHandlerAdapter is initialized by default.
In addition to inheriting from the parent class AbstractHandlerMethodAdapter, RequestMappingHandlerAdapter also implements the InitializingBean and beanfactory aware interfaces.
We know that if a class implements the InitializingBean interface in Spring, the Spring container will call the afterpropertieset method of the Bean when instantiating the Bean.
@Override public void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans 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 = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
In the afterpropertieset method in the RequestMappingHandlerAdapter class, the following four things are mainly done.
1. The initcontrolleradvicecache method is mainly used to initialize the three attributes of initBinderAdviceCache, modelAttributeAdviceCache and requestResponseBodyAdvice in the RequestMappingHandlerAdapter class.
In the initControllerAdviceCache method, the bean with @ ControllerAdvice annotation will be obtained in the Spring container first.
Then traverse each bean in turn, find the methods annotated with @ InitBinder in each bean, and initialize the initBinderAdviceCache attribute of these methods.
Find the methods with @ ModelAttribute annotation in each bean again, and initialize these methods to the attribute modelAttributeAdviceCache.
Collect the bean s that implement the RequestBodyAdvice interface and the ResponseBodyAdvice interface first. Finally, put it at the top of the attribute list of requestResponseBodyAdvice.
2. Initialize the argumentResolvers property of the RequestMappingHandlerAdapter class.
When the argumentResolvers property of the RequestMappingHandlerAdapter class is empty, the getDefaultArgumentResolvers() method will be called to construct the list of default parsers.
The list of parsers is constructed in the order of four types: annotation resolution, type resolution, custom resolution, and all type resolution.
3. Initialize the initBinderArgumentResolvers property of the RequestMappingHandlerAdapter class.
When the initBinderArgumentResolvers property of the RequestMappingHandlerAdapter class is empty, the getDefaultInitBinderArgumentResolvers() method will be called to construct the list of default parsers.
The list of parsers is also constructed in the order of "annotation resolution", "type resolution", "custom resolution" and "all type resolution".
4. Initialize the returnValueHandlers property of the RequestMappingHandlerAdapter class. If the returnValueHandlers property of the RequestMappingHandlerAdapter class is empty, the getDefaultReturnValueHandlers() method will be called to construct the list of default parsers.
The list of parsers is constructed in the order of "single intent", "annotation resolution", "multiple intent", "type resolution", "custom resolution", "all type resolution", etc.
RequestMappingHandlerAdapter inherits the AbstractHandlerMethodAdapter class, which mainly rewrites the three template methods provided by the parent class AbstractHandlerMethodAdapter.
1.supportsInternal method
In the supportsInternal method, it directly returns true. Therefore, it is the supports method in the parent class that really works.
2.getLastModifiedInternal method
The method returns - 1 directly.
3.handleInternal method
This method is the method that actually handles the request. The whole method is roughly divided into three steps.
- Be ready to process all the parameters of the request
- Use the processor to process the request
- Unify the return values of different types into ModelAndView type returns
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; checkRequest(request); // Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } } else { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
As can be seen from the above code, the methods of handleInternal mainly call checkRequest and invokeHandlerMethod.
- checkRequest method
The checkRequest method is mainly used to detect requests. Of the RequestMappingHandlerAdapter class
The checkRequest method actually calls the checkRequest method of the parent class WebContentGenerator.
In the checkRequest method, there are mainly two tests.
- Detect whether the requested method is supported. In the AbstractHandlerMethodAdapter class, since the value of restrictDefaultSupportedMethods is set to false, the detection of the requested method will not be performed.
- Detect whether the requested session exists
- invokeHandlerMethod method
1. First, we will build a ServletWebRequest object with request and response.
2. Build a webdatabinder factory object
3. Build a ModelFactory object
4. Create an instance of ServletInvocableHandlerMethod, through which the actual request is processed. After the ServletInvocableHandlerMethod instance is created, the attribute values of argumentResolvers, returnValueHandlers and parameterNameDiscoverer will be assigned to this instance.
The ServletInvocableHandlerMethod instance uses the invokeAndHandle method to process requests. In the invokeAndHandle method, the invokeForRequest method of the parent class will be called first. Then set the state of Response, and finally use HandlerMethodReturnValueHandler to process the return value.
In the invokeForRequest Method, we will first use the getMethodArgumentValues Method to get all the parameters of the Method call. Then call the doInvoke(args) Method. The doInvoke(args) Method is the Method that actually executes request processing. It is the core Method of the HandlerMethod series. In the doInvoke(args) Method, get the bridge Method of the Method through getBridgedMethod(). Then use the reflection technology of java to call the invoke(getBean(), args) Method of BridgedMethod to execute the specific handler.
WebDataBinderFactory
In the RequestMappingHandlerAdapter class, an instance of webdatabindiderfactory type is created through the getdatabindiderfactory method.
private 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((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { initBinderMethods.add(createInitBinderMethod(bean, method)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean, method)); } return createDataBinderFactory(initBinderMethods); }
In the getDataBinderFactory method, you will first find the Method with @ InitBinder annotation.
initBinderCache is used here The cache is generally searched in the cache first. If it cannot be found, it is searched through the MethodIntrospector.selectMethods method. Finally, put the found methods into the cache.
In the initBinderAdviceCache cache, the global @ InitBinder annotation method is cached. The so-called global method is the method of the @ InitBinder annotation in the @ ControllerAdvice annotation class.
In the code, the global method will be built into an InvocableHandlerMethod object and put into the initBinderMethods collection. Then put the method of the bean type to which the handlerMethod belongs into the collection. Finally, create a servletrequestdatabinder factory object through the initBinderMethods collection.
WebDataBinder factory is a factory class that is specially used to create objects of DataBinder type. The object of DataBinder type is used for parameter binding. Its main function is to realize the type conversion between parameters and strings. ArgumentResolver will use WebDataBinder during parameter resolution. In addition, ModelFactory will also use it when updating the Model.
ModelFactory
In the RequestMappingHandlerAdapter class, an instance of ModelFactory type is created through the getModelFactory method.
private 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((controllerAdviceBean, methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.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 new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); }
The creation process of ModelFactory type instances is very similar to that of webdatabinder factory. It is to find the local method of @ ModelAttribute annotation in the bean type to which handlerMethod belongs. From modelAttributeAdviceCache In the cache, find the global @ ModelAttribute annotation.
Combine the global and local @ ModelAttribute annotation methods to form a list of handlermoved. The global @ ModelAttribute annotation method will be placed in front of this list.
The creation process of ModelFactory is different from that of webdatabinder factory in that there is an additional instance of SessionAttributesHandler. Finally, the list of handlermoved, the instance of sessionattributehandler and binderFactory will be used as parameters to build the instance of ModelFactory.
The ModelFactory class is specially used to maintain models. He mainly did two things.
- The initModel method of the ModelFactory class initializes the Model
The initModel method is mainly used to set the corresponding data into the Model before the processor Handler executes.
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); } } }
From the code of the initModel method, we can see that the initModel method does three events in total.
- Take sessionAttributes from the sessionattributehandler object and merge them into the ModelAndViewContainer object.
- Call the invokemodeleattributemethods method, which is mainly used to execute all @ ModelAttribute annotation methods in turn, and put the final results into the Model.
- Find the parameter name that is both the @ ModelAttribute annotation and the @ SessionAttribute annotation, and then traverse all the parameters to determine whether the parameter is already in the Model. If not, save the parameter name and parameter value in the Model.
- The updateModel method of ModelFactory class is mainly used to update parameters to SessionAttributes. The updateModel method mainly does two things.
- Set the in SessionAttributes.
- Set BindingResult for the required parameters in the Model for use by the view.
ServletInvocableHandlerMethod
Class diagram
As can be seen from the class diagram, the ServletInvocableHandlerMethod class inherits the InvocableHandlerMethod class, and the InvocableHandlerMethod class inherits the parent class HandlerMethod.
HandlerMethod
HandlerMethod class mainly encapsulates the information of handler methods and provides access to method parameters, method return values, method comments and other information.
Properties in the HandlerMethod class
name | type | describe |
bean | Object | The Web controller bean where the Web controller method is located. Can be a string, representing the name of the bean; It can also be the bean instance object itself. |
beanType | Class | The type of the Web controller bean where the Web controller method is located. If the bean is proxied, the user class information of the proxy is recorded here |
method | Method | Web controller method |
bridgedMethod | Method | Bridged Web controller method |
parameters | MethodParameter[] | Parameter information of Web controller method: method, parameter, index and parameter type of the class |
responseStatus | HttpStatus | code attribute of annotation @ ResponseStatus |
responseStatusReason | String | Annotate the reason attribute of @ ResponseStatus |
InvocableHandlerMethod
Invokablehandlermethod class inherits the HandlerMethod class, which adds three properties on the basis of the parent class.
- Databindierfactory: webdatabindierfactory type, mainly used as the parameter of @ InitBinder annotation.
- argumentResolvers: HandlerMethodArgumentResolverComposite type, used for parameter resolvers.
- Parameternamediscoverer: parameternamediscoverer type, used to get the parameter name.
The InvocableHandlerMethod class provides an important Method, invokeForRequest, which mainly calls the Method by reflection.
In fact, the methods of @ InitBinder annotation and @ ModelAttribute annotation are encapsulated into instances of InvocableHandlerMethod type. Calling the invokeforequest method of the InvocableHandlerMethod type instance is to call the methods under these two annotations.
@Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "' with arguments " + Arrays.toString(args)); } Object returnValue = doInvoke(args); if (logger.isTraceEnabled()) { logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) + "] returned [" + returnValue + "]"); } return returnValue; }
The invokeForRequest method mainly performs two events. 1. Through the getMethodArgumentValues method, the parameters required for calling the method are prepared. 2. In the doInvoke method, the method is called by reflection through the bridge method.
ServletInvocableHandlerMethod
The ServletInvocableHandlerMethod class in turn inherits the InvocableHandlerMethod class. It provides the invokeAndHandle method.
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()) { disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true); return; } } else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } }
As can be seen in the code, the invokeAndHandle method of the ServletInvocableHandlerMethod class calls the invokeForRequest method of the parent class first. On this basis, 1. Added the setting of ResponseStatus. 2. The return value is processed.
ModelAndView
The ModelAndView class contains two important properties, Model and View. ModelAndView is mainly used to interact with front-end pages in the background. It can save the data, redirect or forward it to the specified page, and then use the data to render the page.
Model is a ModelMap type, and ModelMap inherits this subclass of LinkedHashMap. The model object is responsible for transferring data between the controller and the view. The data in the model attribute will be copied to the Servlet Response attribute. Model is equivalent to a JOPO object.
View is a view in Spring MVC. The view is used to render data and show the data in the model to the user. The view can be used to redirect or forward to the specified page.
HttpRequestHandlerAdapter
HttpRequestHandlerAdapter is an adapter for a dedicated http request processor. The http request Handler here is a Handler that implements the org.springframework.web An instance of the httprequesthandler interface.
Our custom http request handler needs to implement the handleRequest method of the HttpRequestHandler interface. Implement your own business logic in this method.
The two parameters of HttpServletRequest and HttpServletResponse are provided in the handleRequest method for our use.
SimpleControllerHandlerAdapter
SimpleControllerHandlerAdapter is the adapter of the Controller processor. The Handler of the Controller processor here is an instance that implements the org.springframework.web.servlet.mvc.Controller interface.
Spring MVC provides the abstract class org.springframework.web.servlet.mvc.AbstractController, which implements the Controller interface.
Implement the handleRequest method of the Controller interface in AbstractController, and provide the template method of handleRequestInternal for subclasses to extend.
Generally, we will directly inherit the AbstractController abstract class and override the handleRequestInternal method. In the handleRequestInternal method, we need to construct a ModelAndView object to return.