Relationship with spring
In short, it is the relationship between parent and child containers, but each has different settings. For container creation, you can look at the spring source code. The most special one is that springMVC will register ContextListner, and then finish refresh will trigger the completion of nine components
multicastEvent traverses all listner s and can handle invoke s. The contextrefreshlistener processing method will register and set nine components
The above is the creation of two containers, followed by the service method to accept requests. The final one is doDispatch, which relies on nine components to complete the processing of requests
Focus on doDispatch explanation
1. Check whether it is a file upload request
It mainly judges the processing according to the contentType and parses the uploaded file
processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request);
2. Looking for handlerexecutionchain
Find the handler (handler is the logic we write to handle the request) and interceptor interceptors according to handler mapping
HandlerExecutionChain mappedHandler = null; // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
2.1 finding handler
We need to rely on the handlerMapping component to find the handler
There are two main HandlerMapping implementations: AbstractHandlerMethodMapping and AbstractUrlHandlerMapping. The class diagram is as follows
Before understanding these two classes, you need to understand the three implementation methods of Controller, You can refer to this
- Annotate @ Controller annotation and specify url
- Inherit the Controller class, implement the handleRequest method, and specify the url in the XML
- Inherit HttpRequestHandler, implement the handleRequest method, and specify the url in XML
AbstractHandlerMethodMapping solves the problem of finding handlers with @ Controller mode
How to find? AbstractHandlerMethodMapping internally maintains six sets in the MappingRegistry. It mainly uses mappingLookup to save the mapping from url to method.
Where does mappingLookup initialize? Because AbstractHandlerMethodMapping inherits InitializingBean, the MappingRegistry collection is set by the setProperties method when the container creates the bean
The AbstractHandlerMethodMapping class solves the Controller that inherits the Controller and HTTPRequestHandler
How to find? AbstractHandlerMethodMapping internally saves map < pathpattern, Object > handlermap, which saves the mapping from url to controller object
Where is handlerMap initialized? AbstractHandlerMethodMapping inherits ApplicationContextAware, and invokeAwareMethod will be executed when the container creates this bean
2.2 searching for interceptors
adaptedInterceptors are matched mainly according to the url of the handler. Those that can be matched are added to the interceptor set of the chain.
Where does adaptedInterceptors initialize? Similarly, if you inherit ApplicationContextAware, you will execute invokeAwareMethod to initialize the bean when the container creates it
3.handler corresponding to handlerAdapter
In the above process of finding a handler, the name of each handler method is different, so a layer of adaptation is added to make it possible to call the handle method uniformly. Of course, the adapter is far more than that. Because the subsequent call depends on this class, this class needs to have a function, which is also the most cumbersome implementation and the processing and parsing of parameter values. Let's take the RequestMappingHandlerAdapter we often use
4. Handle last modified
boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } }
This method can help us reduce the number of times to obtain resources
5. prehandler method of intercetor
This is where the prehandler method of the interceptor we often write in the project executes
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
6. The handler corresponds to the handlerAdapter call
// Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
RequestMappingHandlerAdapter mainly deals with the classes marked by @ Controller. I divide them into creation and call. It should be noted that the creation is created when the springMVC container is started. This doDispatch is called, but the creation process must be explained before understanding
6.1 creating
The most important thing for the container to create the RequestMappingHandlerAdapter object is the afterPorpertiesSet method. Here, the following figure sets the properties
- initBinderAdviceCache: set the InitBinder of global configuration. The InitBinder of global type needs to be marked with @ controlleradviceon the declared class
- modelAttributeAdviceCache:: set the ModelAttribute of global configuration. The ModelAttribute of global type needs to be marked on the declared class with @ controlleradvicecache
- argumentResolvers: used to obtain the corresponding ArgumentResolver according to the annotation type annotated by the method parameters of the current handler, such as @ RequestParam, @ ModelAttribute, etc., so as to convert the parameters in the request to the type of the corresponding annotation in the current method
- returnValueHandlers: adapt the return value through ReturnValueHandler
@InitBinder can bind request parameters to the specified attribute editor, such as
- Trim the passed string by trim
- Convert the date of string type to java Date type
6.2 calling
Call also has preparation procedure and call procedure
Preparation: initBinder, modelAttribute, parameter processor, return value processor, etc
6.2.1 preparation
It can be seen that the values of the previous settings for creating RequestMappingHandlerAdapter are used, but there are two more InitBinderCache, modelAttributeCache, which are local @ InitBinderCache and @ ModelAttribute. They only take effect in the defined class, not global. In this way, we need to set both global and local to the preparation material of this call process. The process of getting the required InitBinder and ModelAttribute code is very type (get the local code first, then the global code, and finally the cache). Get the required InitBinder as follows
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);
First, get the local initBinder cache. If there is no selectMethods, find the local initBinder; Then get the global. After that, the global and local are encapsulated into initBinderMethods and returned
6.2.2 calling
//Get the value restover private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
- Introduction to important return value processors
If we label @ ResponseBody, json will be returned. Here is the role of returnValueHanlders. These handlers implement two methods: supportsreturnantype and handlerturnantype
The returnValueHanlders for processing @ ResponseBody is the RequestResponseBodyMethodProcessor class, and its processing flow is as follows
7. Apply default view name
applyDefaultViewName(processedRequest, mv); /** * Do we need view name translation? */ private void applyDefaultViewName(HttpServletRequest request, @Nullable ModelAndView mv) throws Exception { if (mv != null && !mv.hasView()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { mv.setViewName(defaultViewName); } } }
8. Execution of interceptor's postHandler
mappedHandler.applyPostHandle(processedRequest, response, mv);
9. Exception handling
It is written inside processHandlerException. If an exception occurs in the previous steps and is caught, it will be detected here for processing
if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } }
Exception handling is not complicated. The complexity is that exception handling is also a method, which also involves the processing of parameters and return values. It is very similar to the handler method executed by the adapter before, as shown in the following figure
10. Page rendering
Here, we need to be very important. According to whether we have mv, if we have mv, we will enter the view rendering, otherwise we will return the original data.
// Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv, request, response); if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } }
It's also easy to understand here. How to return the original data? I'll take the returnValueHanlders of @ ResponseBody as the RequestResponseBodyMethodProcessor class
When processing again, there is a key statement in the entry, which indicates that mv is not required
mavContainer.setRequestHandled(true);
Then, the last getModelAndView method of the handle of the adapter is executed as follows: if mavContainer.isRequestHandled() is true, null will be returned, and the mv will be empty if NULL is returned, so the view will not be directly returned after rendering
modelFactory.updateModel(webReqest, mavContainer); if (mavContainer.isRequestHandled()) { return null; } ModelMap model = mavContainer.getModel(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request != null) { RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } } return mav;
11. Execute trigger after completion of interceptor
if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, null); }