SpringMVC Execution Process Resolution

Spring is known to people who have done java development, but even if they don't know it now, they will gradually know that because of its huge size and many modules, I will introduce the Spring MVC that is used in business development (90% of people are doing business development after years of experience).

        

First, let's look at how Spring MVC handles user requests from the perspective of four components of Spring MVC: the Dispatcher Servlet, the Handler Mapping, the Handler Adapter, and the ViewResolver.

SpringMVC Execution Process

  1. User requests are sent to the front-end controller Dispatcher Servlet.
  2. When the front-end controller Dispatcher Servlet receives the request, Dispatcher Servlet uses Handler Mapping to process it, and Handler Mapping finds the specific Handler object that handles the request.
  3. When Handler Mapping finds the corresponding Handler, it does not return a Handler original object, but a Handler Execution Chain, which includes an interceptor and a Handler that handles requests.HandlerMapping returns an execution chain to Dispatcher Servlet.
  4. Once the Dispatcher Servlet receives the execution chain, it calls the Handler adapter to execute the Handler.
  5. After the Handler Adapter executes the Handler, it gets a ModelAndView and returns it to the Dispatcher Servlet.
  6. Once the Dispatcher Servlet receives the ModelAndView returned by the Handler Adapter, it calls ViewResolver based on the view name in it.
  7. ViewResolver parses the logical view name into a real View view and returns it to Dispatcher Servlet.
  8. Once a Dispatcher Servlet receives a view, it populates it with data based on the model in the ModelAndView above, also known as view rendering.
  9. Once rendering is complete, Dispatcher Servlet can return the results to the user.

Now that you have an overview of the execution process, let's start with the source code https://github.com/spring-projects/spring-framework/ Explore in depth from an angle.

Source Parsing

First, when we access the url, we will send the request to the front-end controller Dispatcher Servlet, which is a Servlet. We know that when a Servlet processes a request, it will be handed over to the service method, and this is no exception. Dispatcher Servlet inherits the FrameworkServlet and first enters the service method of the FrameworkServlet:

 1 protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {    
 2  // Request Method   
 3  HttpMethod httpMethod =HttpMethod.resolve(request.getMethod());   
 4  // If the method is PATCH Method or empty is handled separately   
 5  if (httpMethod == HttpMethod.PATCH || httpMethod == null) {        
 6      processRequest(request, response);   
 7  }   else {
 8     // Other methods of request type pass through the parent class, that is HttpServlet Handle        
 9      super.service(request, response);    
10 }
11 }

The methods doGet or doPost are invoked in HttpServlet, depending on the type of request. These methods have been overridden in FrameworkServlet, where processRequest is invoked for processing, and doService is invoked in processRequest, which is implemented in Dispatcher Servlet.Here's a look at the implementation of the doService method in Dispatcher Servlet.

Dispatcher Servlet Receives Request

doService method in Dispatcher Servlet:

 1 protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     logRequest(request);
 3     // to request Make a snapshot of the attributes in so that the original attributes can be restored
 4     Map<String, Object> attributesSnapshot = null;
 5     if (WebUtils.isIncludeRequest(request)) {
 6         attributesSnapshot = new HashMap<>();
 7         Enumeration<?> attrNames = request.getAttributeNames();
 8         while (attrNames.hasMoreElements()) {
 9             String attrName = (String) attrNames.nextElement();
10             if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
11                 attributesSnapshot.put(attrName, request.getAttribute(attrName));
12             }
13         }
14     }
15     // If no localization or theme processor is configured, SpringMVC The default configuration file is used, that is DispatcherServlet.properties
16     request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
17     request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
18     request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
19     request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
20     if (this.flashMapManager != null) {
21         FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
22         if (inputFlashMap != null) {
23             request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
24         }
25         request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
26         request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
27     }
28 
29     try {
30         // Start real processing
31         doDispatch(request, response);
32     }
33     finally {
34         if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
35             // Restore original property snapshot
36             if (attributesSnapshot != null) {
37                 restoreAttributesAfterInclude(request, attributesSnapshot);
38             }
39         }
40     }
41 }
Next, the Dispatcher Servlet begins the real processing. Let's look at the doDispatch method, which first gets the current requested Handler execution chain, then finds the appropriate Handler Adapter, and then calls the handle method of the RequestMapping HandlerAdapter, as follows: the doDispatch method:
 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     HttpServletRequest processedRequest = request;
 3     HandlerExecutionChain mappedHandler = null;
 4     boolean multipartRequestParsed = false;
 5     WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
 6     try {
 7         ModelAndView mv = null;
 8         Exception dispatchException = null;
 9         try {
10             // Check first if it is Multipart Type, such as upload; if yes Multipart Type is converted to MultipartHttpServletRequest type
11             processedRequest = checkMultipart(request);
12             multipartRequestParsed = (processedRequest != request);
13 
14             // Get the current request's Handler execution chain
15             mappedHandler = getHandler(processedRequest);
16             if (mappedHandler == null) {
17                 noHandlerFound(processedRequest, response);
18                 return;
19             }
20 
21             // Get the current request's Handler Adapter
22             HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
23 
24             // about header in last-modified Processing
25             String method = request.getMethod();
26             boolean isGet = "GET".equals(method);
27             if (isGet || "HEAD".equals(method)) {
28                 long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
29                 if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
30                     return;
31                 }
32             }
33 
34             // Traverse through all definitions interceptor,implement preHandle Method
35             if (!mappedHandler.applyPreHandle(processedRequest, response)) {
36                 return;
37             }
38 
39             // Actual call Handler Places
40             mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
41 
42             if (asyncManager.isConcurrentHandlingStarted()) {
43                 return;
44             }
45             // Processing to default view name, i.e. adding prefix, suffix, etc.
46             applyDefaultViewName(processedRequest, mv);
47             // Interceptor postHandle Method Processing
48             mappedHandler.applyPostHandle(processedRequest, response, mv);
49         }
50         catch (Exception ex) {
51             dispatchException = ex;
52         }
53         catch (Throwable err) {
54             dispatchException = new NestedServletException("Handler dispatch failed", err);
55         }
56         // Processing the final result, rendering and so on are all here
57         processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
58     }
59     catch (Exception ex) {
60         triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
61     }
62     catch (Throwable err) {
63         triggerAfterCompletion(processedRequest, response, mappedHandler,
64                 new NestedServletException("Handler processing failed", err));
65     }
66     finally {
67         if (asyncManager.isConcurrentHandlingStarted()) {
68             if (mappedHandler != null) {
69                 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
70             }
71         }
72         else {
73             if (multipartRequestParsed) {
74                 cleanupMultipart(processedRequest);
75             }
76         }
77     }
78 }


Find the corresponding Handler object

Let's explore how to get the current requested Handler execution chain, corresponding to this code: mappedHandler = getHandler(processedRequest);Look at the Dispatcher Servlet specific getHandler method, which essentially traverses through all handler Mappings for processing. Handler Mappings are pre-registered at startup, and the getHandler method in the AbstractHandler Mapping class is called in the loop to get the handler execution chain. If the obtained Handler execution chain is notNull, returns the currently requested Handler execution chain, and the Dispatcher Servlet class's getHandler method is as follows:

 1 protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 2     if (this.handlerMappings != null) {
 3         // Traverse all handlerMappings Processing, handlerMappings Pre-registered at startup
 4         for (HandlerMapping mapping : this.handlerMappings) {
 5             HandlerExecutionChain handler = mapping.getHandler(request);
 6             if (handler != null) {
 7                 return handler;
 8             }
 9         }
10     }
11     return null;
12 }

 

In the loop, according to mapping.getHandler(request); continue looking at the getHandler method in the AbstractHandlerMapping class:
 1 public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
 2     // according to request Obtain handler
 3     Object handler = getHandlerInternal(request);
 4     if (handler == null) {
 5         // Use default if not found handler
 6         handler = getDefaultHandler();
 7     }
 8     if (handler == null) {
 9         return null;
10     }
11     // If Handler yes String,Indicates a bean Name, need to find a corresponding bean
12     if (handler instanceof String) {
13         String handlerName = (String) handler;
14         handler = obtainApplicationContext().getBean(handlerName);
15     }
16     // encapsulation Handler execution chain
17     return getHandlerExecutionChain(handler, request);
18 }

 


The getHandler method in the AbstractHandlerMapping class first obtains the handler from requrst, primarily by calling the getHandlerInternal method in the AbstractHandlerMethodMapping class, which first obtains the url in the request, /testSpringMvc, to match the handler and encapsulate it as a handlerMethod, then instantiates it from the bean s in the handlerMethodHandler and return.
 1 protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
 2     // Obtain request In url,To match handler
 3     String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
 4     request.setAttribute(LOOKUP_PATH, lookupPath);
 5     this.mappingRegistry.acquireReadLock();
 6     try {
 7         // Find by Path Handler,And encapsulated into HandlerMethod
 8         HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
 9         // according to handlerMethod In bean To instantiate Handler,And add HandlerMethod
10         return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
11     }
12     finally {
13         this.mappingRegistry.releaseReadLock();
14     }
15 }

Next, let's look at the logic of the lookupHandlerMethod, which is delegated to the mappingRegistry member variable to handle:
 1 protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
 2     List<Match> matches = new ArrayList<>();
 3     // adopt lookupPath Search in properties.If found, return the corresponding RequestMappingInfo
 4     List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
 5     if (directPathMatches != null) {
 6         // If it does, check that other attributes, such as request method, parameters, meet the requirements. header etc.
 7         addMatchingMappings(directPathMatches, matches, request);
 8     }
 9     if (matches.isEmpty()) {
10         // Without a direct match, all processing methods are traversed for matching wildcards
11         addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
12     }
13 
14     if (!matches.isEmpty()) {
15         // If the method has multiple matches, different wildcards, etc., then sort to select the most appropriate one
16         Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
17         matches.sort(comparator);
18         Match bestMatch = matches.get(0);
19         // If there are multiple matches, a second most appropriate comparison will be found
20         if (matches.size() > 1) {
21             if (logger.isTraceEnabled()) {
22                 logger.trace(matches.size() + " matching mappings: " + matches);
23             }
24             if (CorsUtils.isPreFlightRequest(request)) {
25                 return PREFLIGHT_AMBIGUOUS_MATCH;
26             }
27             Match secondBestMatch = matches.get(1);
28             if (comparator.compare(bestMatch, secondBestMatch) == 0) {
29                 Method m1 = bestMatch.handlerMethod.getMethod();
30                 Method m2 = secondBestMatch.handlerMethod.getMethod();
31                 String uri = request.getRequestURI();
32                 // Cannot have the same optimum Match
33                 throw new IllegalStateException(
34                         "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
35             }
36         }
37         request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
38         // Set up request Parameters ( RequestMappingHandlerMapping Override it)
39         handleMatch(bestMatch.mapping, lookupPath, request);
40         // Return matching url Processing method
41         return bestMatch.handlerMethod;
42     }
43     else {
44         // call RequestMappingHandlerMapping Class handleNoMatch Method Match Again
45         return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
46     }
47 }

From the above process, we get the Handler and begin encapsulating the execution chain by adding our configured interceptors to the execution chain. The getHandler ExecutionChain method is as follows:
 1 protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
 2     // If current Handler Instead of an execution chain type, encapsulate it with a new execution chain instance
 3     HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
 4 
 5     String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
 6     // Traverse the interceptor to find the current url Correspondingly, add to the execution chain
 7     for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
 8         if (interceptor instanceof MappedInterceptor) {
 9             MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
10             if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
11                 chain.addInterceptor(mappedInterceptor.getInterceptor());
12             }
13         }
14         else {
15             chain.addInterceptor(interceptor);
16         }
17     }
18     return chain;
19 }

So far, we have obtained the current request's Handler execution chain. Next, we will look at how to get the requested Handler adapter, relying primarily on the Dispatcher Servlet class's getHandler Adapter method, which iterates through all Handler Adapters and returns those that match the current Handler, where the RequestMappingHandler Adapter is matched.

The getHandlerAdapter method of the Dispatcher Servlet class is as follows:

 1 protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
 2     if (this.handlerAdapters != null) {
 3         // Traverse all HandlerAdapter,Find and Current Handler Return if matched
 4         for (HandlerAdapter adapter : this.handlerAdapters) {
 5             if (adapter.supports(handler)) {
 6                 return adapter;
 7             }
 8         }
 9     }
10     throw new ServletException("No adapter for handler [" + handler +
11             "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
12 }

HandlerAdapter executes the current Handler

Once you have obtained the currently requested Handler adapter, you proceed to cache processing, which is the processing of last-modified, and then call the applyPreHandle method to execute the interceptor's preHandle method, which is to traverse all defined interceptors, execute the preHandle method, and then to where the handle actually executes, the handle method in the doDispatch methodThe method is to execute the current Handler, and we are using the RequestMappingHandlerAdapter, which first enters the handle method of the AbstractHandler MethodAdapter:

1 public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
2     return handleInternal(request, response, (HandlerMethod) handler);
3 }

In the handle method of AbstractHandlerMethodAdapter, the handleInternal method of the RequestMappingHandlerAdapter class is called again:
 1 protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
 2     ModelAndView mav;
 3     checkRequest(request);
 4     if (this.synchronizeOnSession) {
 5         HttpSession session = request.getSession(false);
 6         if (session != null) {
 7             Object mutex = WebUtils.getSessionMutex(session);
 8             synchronized (mutex) {
 9                 mav = invokeHandlerMethod(request, response, handlerMethod);
10             }
11         }
12         else {
13             mav = invokeHandlerMethod(request, response, handlerMethod);
14         }
15     }
16     else {
17         // Execution method, encapsulation ModelAndView
18         mav = invokeHandlerMethod(request, response, handlerMethod);
19     }
20     if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
21         if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
22             applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
23         }
24         else {
25             prepareResponse(response);
26         }
27     }
28     return mav;
29 }

After executing the handle method, then call the applyDefaultViewName method to assemble the default view name, add the prefix and suffix names, and then call the applyPostHandle method to execute the interceptor's preHandle method, which is to iterate through all defined interceptors and execute the preHandle method.

Processing final results and rendering

Finally, the processDispatchResult method in the Dispatcher Servlet class is called, which handles the final result mainly, including exception handling, rendering the page, and triggering the interceptor's afterCompletion() method execution by issuing a completion notification.The processDispatchResult() method code is as follows:

 1 private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
 2     boolean errorView = false;
 3     if (exception != null) {
 4         if (exception instanceof ModelAndViewDefiningException) {
 5             logger.debug("ModelAndViewDefiningException encountered", exception);
 6             mv = ((ModelAndViewDefiningException) exception).getModelAndView();
 7         }
 8         else {
 9             Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
10             mv = processHandlerException(request, response, handler, exception);
11             errorView = (mv != null);
12         }
13     }
14     if (mv != null && !mv.wasCleared()) {
15         // Rendering
16         render(mv, request, response);
17         if (errorView) {
18             WebUtils.clearErrorRequestAttributes(request);
19         }
20     }
21     else {
22         if (logger.isTraceEnabled()) {
23             logger.trace("No view rendering, null ModelAndView returned.");
24         }
25     }
26     if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
27         return;
28     }
29     if (mappedHandler != null) {
30         mappedHandler.triggerAfterCompletion(request, response, null);
31     }
32 }
Let's see how the render method of the Dispatcher Servlet class accomplishes rendering. The render method of the Dispatcher Servlet class renders as follows:
  1. To determine whether a view in ModelAndView is a view name, its instance object is not obtained: if it is based on name, resolveViewName needs to be called to get the corresponding view object from the view parser; otherwise, the getview method is used in ModelAndView to get the view object.
  2. The render method of the View class is then called.

The render method of the Dispatcher Servlet class is as follows:

 1 protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
 2     // Set up localization
 3     Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
 4     response.setLocale(locale);
 5     View view;
 6     String viewName = mv.getViewName();
 7     if (viewName != null) {
 8         // Resolve the view name to get the view
 9         view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
10         if (view == null) {
11             throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
12                     "' in servlet with name '" + getServletName() + "'");
13         }
14     }
15     else {
16         view = mv.getView();
17         if (view == null) {
18             throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
19                     "View object in servlet with name '" + getServletName() + "'");
20         }
21     }
22 
23     if (logger.isTraceEnabled()) {
24         logger.trace("Rendering view [" + view + "] ");
25     }
26     try {
27         if (mv.getStatus() != null) {
28             response.setStatus(mv.getStatus().value());
29         }
30         // Delegate rendering to view
31         view.render(mv.getModelInternal(), request, response);
32     }
33     catch (Exception ex) {
34         if (logger.isDebugEnabled()) {
35             logger.debug("Error rendering view [" + view + "]", ex);
36         }
37         throw ex;
38     }
39 }

Assuming we are using the Thymeleaf template engine (there are other template engines such as freemarker, etc.), view.render finds the render method of the corresponding view ThymeleafView to render.
1 public void render(final Map<String, ?> model, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
2     renderFragment(this.markupSelectors, model, request, response);
3 }

ThymeleafView's render method calls the renderFragment method to render the view, and after rendering is complete, Dispatcher Servlet can return the result to us.

 

summary

The execution process of springmvc is simply described (inadequacies are pointed out more), the processing process of web framework can be said to be the same way (I have developed with djang o, python's web framework before), if I do not look at the programming language, I would think it is django process!!!, all for rapid development of fast iteration business systems, unlike the earlier need to write native servlet s, such as

Tags: Java Spring Session snapshot

Posted on Fri, 03 Apr 2020 23:20:09 -0700 by papaface