How exactly does SpringMVC handle requests?

A lot of people use SpringMVC, but they don’t know how it handles requests. When we learn something, understanding it makes it easier to use it. Let’s take a look at how SpringMVC handles requests. The manner in which the request flow is made

First above:

Spring MVC framework is also a Web framework based on request driver, and use the front controller pattern (are used to provide a centralized request processing mechanism, all requests will be handled by a single processor to design, then distributed to the corresponding page request according to the mapping rule controller (action/processor) for processing. Let’s take a look at the Spring MVC request process as a whole:

First, the user sends a request, which is captured by the SpringMVC front-end controller (DispatherServlet). The front-end controller (DispatherServlet) parses the REQUEST URL to obtain the request URI, and invokes HandlerMapping according to the URI. The front-end controller (DispatherServlet) retrieves the returned HandlerExecutionChain (including the Handler object and its corresponding interceptor); The DispatcherServlet selects an appropriate HandlerAdapter based on the HandlerExecutionChain obtained. (Note: If the HandlerAdapter is successfully obtained, execution of the interceptor's preHandler(...) begins at this point.) Methods); The HandlerAdapter ADAPTS and executes the Handler based on the requested Handler; The HandlerAdapter extracts the model data from the Request, populates the Handler entry parameter, and starts executing Handler (Controller). During the entry process of populating the Handler, Spring does some additional work, depending on the configuration: HttpMessageConveter: Converts the request message (such as Json, XML, etc.) into an object, which is converted into the specified response information; Data transformation: Data transformation of the request message. Such as String to Integer, Double, and so on; Data formatting: converting a string to a formatted number or date, etc. Data validation: verifies the validity of the data (length, format, etc.) and stores the validation results in BindingResult or Error); When this Handler completes, it returns a ModelAndView to the HandlerAdaptor. The HandlerAdaptor adapter returns the execution result, ModelAndView, to the front-end controller; After receiving the ModelAndView, the front-end controller requests the corresponding view parser. The View parser parses the ModelAndView and returns the corresponding View. Render the view and return the rendered view to the front-end controller; Finally, the front-end controller sends the rendered page response to the user or client.Copy the code

Example implementation of SpringMVC request source code interpretation

For the SpringMVC project all request entries (except static resources) start here with the front-end controller DispatcherServlet configured in the web.xml file:

The UML inheritance diagram of DispatcherServlet is as follows:

The blue line inheritance structure is DispatcherServlet — >FrameworkServlet — >HttpServletBean — >HttpServlet — >GenericServlet — >Servlet.

For handling web requests, we all know that we override the service method by inheriting HttpServlet. If we open the DispatcherServlet source code, we do not see the service method we are looking for. You can see that the superclass overrides the HttpServlet service method. FrameworkServlet#service

/ * *

  • Override the parent class implementation in order to intercept PATCH requests.

*/ @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); if (httpMethod == HttpMethod.PATCH || httpMethod == null) { processRequest(request, response); } else { super.service(request, response); }}

From the source code analysis method when the request for the patch request or to null processRequest0 execution method, other what it calls the superclass method of service, we all know, the SpringMVC request most is get | post request is given priority to, HttpServletBean () — > HttpServlet service (); HttpServlet#service

@Override public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException { HttpServletRequest request; HttpServletResponse response; if (! (req instanceof HttpServletRequest && res instanceof HttpServletResponse)) { throw new ServletException("non-HTTP request or response"); } request = (HttpServletRequest) req; response = (HttpServletResponse) res; service(request, response); }Copy the code

}

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod();

if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); if (ifModifiedSince < lastModified) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); }}Copy the code

The HttpServlet service is overloaded and calls different methods depending on the request type. For example, if the request method is GET request, the HttpServlet service is overloaded and calls doGet request. Httpservlets have doGet implementations, but there are also doGet implementations in inherited subclasses. Which method is called? It’s obvious to call a subclass’s doGet method (object-oriented polymorphism!!). FrameworkServlet: FrameworkServlet#doGet& ProcessRequest

@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

processRequest(request, response); }

protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// System startTime long startTime = system.currenttimemillis (); Throwable failureCause = null; . / / the internationalization LocaleContext previousLocaleContext = LocaleContextHolder getLocaleContext (); LocaleContext localeContext = buildLocaleContext(request); / / build ServletRequestAttributes object RequestAttributes previousAttributes = RequestContextHolder. GetRequestAttributes (); ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); . / / asynchronous management WebAsyncManager asyncManager = WebAsyncUtils getAsyncManager (request); asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // Initialize ContextHolders initContextHolders(Request, localeContext, requestAttributes); try { doService(request, response); } catch (ServletException | IOException ex) { failureCause = ex; throw ex; } catch (Throwable ex) { failureCause = ex; throw new NestedServletException("Request processing failed", ex); } finally {// Restore the original LocaleContext and ServiceRequestAttributes to LocaleContextHolder and RequestContextHolder. For example, Filter resetContextHolders(Request, previousLocaleContext, previousAttributes); if (requestAttributes ! = null) { requestAttributes.requestCompleted(); } logResult(request, response, failureCause, asyncManager); / / release ServletRequestHandlerEvent message, If the request execution success will release publishRequestHandledEvent (request, response, startTime, failureCause); }}Copy the code

// initContextHolders(request, localeContext, requestAttributes); private void initContextHolders(HttpServletRequest request, @Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {

if (localeContext ! = null) { LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable); } if (requestAttributes ! = null) { RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable); }}Copy the code

The method does a few things: Internationalize the setup, create a ServletRequestAttributes object, and initialize the context holders. If you want to obtain request or response objects from a method, you can call LocaleContextHolder and then call doService. The FrameworkServlet class does not provide an implementation for the doService method, which is implemented by the DispatcherServlet subclass: DispatcherServlet#doService

The entry method for the DispatcherServlet is doService. Since this class inherits from the FrameworkServlet class, it overrides the doService() method:

@Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { logRequest(request);

// Keep a snapshot of the request attributes in case of an include, // to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null; if (WebUtils.isIncludeRequest(request)) { attributesSnapshot = new HashMap<>(); Enumeration<? > attrNames = request.getAttributeNames(); while (attrNames.hasMoreElements()) { String attrName = (String) attrNames.nextElement(); if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) { attributesSnapshot.put(attrName, request.getAttribute(attrName)); }}}

/ / the Spring contextCopy the code

request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); Request. setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeresolver); Request. setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeresolver); // theme request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

// Redirect dataCopy the code

if (this.flashMapManager ! = null) { FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response); if (inputFlashMap ! = null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager); }

// Request request doDispatch(request, response); } finally { if (! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Restore the original attribute snapshot, in case of an include. if (attributesSnapshot ! = null) { restoreAttributesAfterInclude(request, attributesSnapshot); }}}}

The entire method looks like this: Handle requests for include tags, put the context in the request properties, put the internationalization parser in the Request properties, put the topic parser in the Request properties, put the topic in the Request properties, The redirected request data is processed by calling the doDispatch core method: DispatcherServlet#doDispatch

This method is called in the doService method and designs the entire request processing flow from the bottom up:

Find the Handler according to the request. Find the corresponding HandlerAdapter according to the Handler. Use the HandlerAdapter to handle the Handler and call processDispatchResult Method to process the results from above (including the View rendered and output to the user)Copy the code

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try { ModelAndView mv = null; Exception dispatchException = null;

// Check whether it is an upload request. Yes Upload request parsing is performed. Otherwise, request processedRequest = checkMultipart(request) is returned. multipartRequestParsed = (processedRequest ! = request); HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain = HandlerExecutionChain HandlerInterceptor mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; HandlerAdapter ha = getHandlerAdapter(mappedHandler.gethandler ()); // Last-modified String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); // If the data has not changed, return the last data directly. If (new ServletWebRequest(Request, response).checknotModified (lastModified) &&isget) {return; }} return Interceptor's preHandle if (! mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Execute Handler to return ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.gethandler ()); / / for asynchronous processing, direct return the if (asyncManager. IsConcurrentHandlingStarted () {return; } // When the view is empty, set the default view according to the request. For example, the Handler returns void applyDefaultViewName(processedRequest, mv); . / / the implementation of corresponding Interceptor postHandle mappedHandler applyPostHandle (processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) {As of 4.3, we're processing Errors thrown from handler methods As well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } // Handle the returned results, including handling exceptions, rendering pages, AfterCompletion processDispatchResult triggers the Interceptor (processedRequest, Response, mappedHandler, mv, dispatchException);Copy the code

} catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException(“Handler processing failed”, err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler ! = null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); }}}}

DoDispatcher first checks whether the upload request, if it is then converts the request to MultipartHttpServletRequest, and multipartRequestParsed flag is set to true; GetHandler Handler chain by getHandler; Process last-Modified of GET and HEAD requests. Here, it mainly determines whether last-Modified value is Modified to process and decide whether to use cached data. The corresponding Interceptor's preHandle is called in turn to intercept the Interceptor. After the interceptor preHandle is executed, the Handler will be routed through the HandlerAdapter to the corresponding Handler (the Controller method is actually executed). After the Handler has processed the request, if asynchronous processing is required, the Handler will return the request directly. If asynchronous processing is not required, set the default view when the view is empty, and then execute the corresponding Interceptor's postHandle. Handler: Processor, which directly corresponds to C in MVC, also known as Controller layer, has many specific manifestations, including classes and methods (methods are usually the most common), because its definition is Object. All of the @requestMapping methods we annotated in the method can be treated as a Handler, anything that can actually handle the request can be treated as a Handler. HandlerMapping: SpringMVC handles many requests. Each request requires a Handler. In this case, HandlerMapping is used to search for the request. HandlerAdapter: Adapters. Different handlers need to find different handlerAdapters to call handlers. Just as tools are needed in a factory, handlerAdapters use handleradapters to complete tasks, and HandlerMapping is used to find tools based on what needs to be done.Copy the code

DispatcherServlet#processDispatchResult

The processDispatchResult method is used to process the previously returned results, including handling the exception, rendering the page, and the afterCompletion method that triggers the Interceptor. The exception handled was generated during the processing of the request doDispatch method.

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {

boolean errorView = false; // If an exception is thrown during the request, handle the exception 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); }}

// Render the page 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.”); }}

if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; }

AfterCompletion of the Interceptor request, trigger the afterCompletion if (mappedHandler! = null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); }}

Render view:

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver ! = null ? this.localeResolver.resolveLocale(request) : request.getLocale()); response.setLocale(locale);

View view; String viewName = mv.getViewName(); if (viewName ! = null) { // We need to resolve the view name. view = resolveViewName(viewName, mv.getModelInternal(), locale, request); if (view == null) { throw new ServletException(“Could not resolve view with name ‘” + mv.getViewName() + “‘ in servlet with name ‘” + getServletName() + “‘”); } } else { // No need to lookup: the ModelAndView object contains the actual View object. view = mv.getView(); if (view == null) { throw new ServletException(“ModelAndView [” + mv + “] neither contains a view name nor a ” + “View object in servlet with name ‘” + getServletName() + “‘”); }}

if (logger.isTraceEnabled()) { logger.trace(“Rendering view [” + view + “] “); } try { if (mv.getStatus() != null) { response.setStatus(mv.getStatus().value()); } // 渲染页面处理 view.render(mv.getModelInternal(), request, response); } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug(“Error rendering view [” + view + “]”, ex); } throw ex; } }

Today we come to understand the MVC framework MVC core ideas, SpringMVC internal request process analysis and source code level interpretation, so that we can really understand the whole framework from the bottom level of the original appearance of the implementation, and finally to summarize today’s source analysis process with a graph.

Extensions to the MVC

Model-view-controller (MVC) is a well-known design idea based on designing interface applications. It decouples the business logic from the interface by separating the roles of the model, view, and controller in the application. Typically, the model is responsible for encapsulating application data for presentation at the view layer. The view simply presents the data and does not contain any business logic. The controller receives the request from the user and invokes the background service (Service or DAO) to process the business logic. After processing, the back-end business layer may return some data to be displayed in the view layer. The controller collects this data and prepares the model for presentation at the view layer. The core idea of the MVC pattern is to separate the business logic from the interface, allowing them to change individually without affecting each other.

The view + “] “, the ex); } throw ex; }}

Today we come to understand the MVC framework MVC core ideas, SpringMVC internal request process analysis and source code level interpretation, so that we can really understand the whole framework from the bottom level of the original appearance of the implementation, and finally to summarize today’s source analysis process with a graph.

img-ryuz9ebi-1607912336441

Extensions to the MVC

Model-view-controller (MVC) is a well-known design idea based on designing interface applications. It decouples the business logic from the interface by separating the roles of the model, view, and controller in the application. Typically, the model is responsible for encapsulating application data for presentation at the view layer. The view simply presents the data and does not contain any business logic. The controller receives the request from the user and invokes the background service (Service or DAO) to process the business logic. After processing, the back-end business layer may return some data to be displayed in the view layer. The controller collects this data and prepares the model for presentation at the view layer. The core idea of the MVC pattern is to separate the business logic from the interface, allowing them to change individually without affecting each other.

imG-qK8MGYGP-1607912336450