What is MVC?

MVC (Model-view-Controller) : It is a software architecture design pattern, which is divided into three parts:

  • Model: The data Model of the business;
  • View: Visualization of the data model;
  • Controller: The Controller that connects patterns and views.

Its main purpose is to modularize code in layers that reduce coupling between layers, with each module conforming to the principle of a single responsibility.

Many Web frameworks of applications are designed based on THE MVC pattern, and Spring is no exception. Spring Web MVC also provides a Web framework based on MVC, commonly known as SpringMVC.

The preparatory work

We are already familiar with the use of SpringMVC in practical development, so let’s introduce some basic knowledge of SpringMVC before parsing the source code.

Supported functions

As a Web framework, SpringMVC also provides a lot of rich functionality:

  • Type conversion: Data formatting of various numeric and date types is supported by default, as well as custom formatting and conversion.
  • Validation: Global or local validation of request parameters. Jsr-303 and HibernateValidator are supported.
  • Interceptor: Registers interceptors to intercept incoming requests.
  • Content type: Custom request content type resolution, such as JSON, XML, and so on.
  • Message Converters: Custom message converters serialize and deserialize different types of messages, Jackson by default.
  • View Controller: initializes some default URL request path corresponding to the page, such as home, 404, 500, etc.
  • View parsers: Parsers that configure views, such as Thymeleaf, Freemarker, Velocity, etc., use JSP, Jackson by default.
  • Static resources: Provides the URL configuration of some static resources.
  • Servlet configuration: for SpringMVC provides DispatcherServlet to override the default DefaultServletHttpRequestHandler processing, support custom Servlet configuration.
  • Path matching: Customize options related to path matching and URL handling.

DispatcherServlet

Let’s look at its class diagram,

It is the front-end controller, the core of SpringMVC, and also a subclass of Servlet. Its main function is to process requests and perform request mapping, view resolution, exception handling and other functions through configurable components. We can think of it as a real Servlet in SpringMVC.

The Servlet configuration

Like IOC, AOP, etc., Servlet configuration of SpringMVC also supports two configuration modes, respectively:

  • XML configuration: Prior to Servlet3.0, servlets were usually configured via web.xml,

    <web-app>
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/app-context.xml</param-value>
        </context-param>
        <servlet>
            <servlet-name>app</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value></param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>app</servlet-name>
            <url-pattern>/app/*</url-pattern>
        </servlet-mapping>
    </web-app>
    Copy the code

    To complete the initialization of front-end controller DispatcherServlet, request mapping, view parsing and other functions; All URL mapping paths and interceptors are configured in XML. Although it is convenient for unified management and maintenance, the configuration is relatively cumbersome, and different functions are highly coupled and not flexible enough.

  • Java code configuration: a new feature after Servlet3.0 supports annotation-based configuration instead of web.xml, so in SpringMVC we can configure it using Java code,

    public class MyWebApplicationInitializer implements WebApplicationInitializer {
        @Override
        public void onStartup(ServletContext servletContext) {
            AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
            // Register the configuration class
            context.register(AppConfig.class);
            / / create the DispatcherServlet
            DispatcherServlet servlet = new DispatcherServlet(context);
            ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
            registration.setLoadOnStartup(1);
            registration.addMapping("/app/*"); }}Copy the code

    Use with web.xml is also supported. The most commonly used SprongBoot in development today relies on Java configuration to configure SpringMVC.

The Bean

SpringMVC defines nine special beans to handle requests and render returns for different strategies. They have a clear division of work and do not interfere with each other. They are:

  • MultipartResolver: a file parser that resolves policies for multi-part requests, including file uploads.
  • LocaleResolver: An internationalized locale parser that automatically resolves the locale Settings of the client, including the time zone, request headers, Cookoe, session, and locale.
  • ThemeResolver: Theme parser for resolving styles of custom static resources.
  • HandlerMapping: Request mapper, the handler responsible for the actual request, like the class or method that configures the @RequestMapping annotation.
  • HandlerAdapter: Request processing adapter used for parsing requests, such as parameter adaptation, reflection calls, etc.
  • HandlerExceptionResolver: request exception resolver, used to resolve the exception resolution policy, such as error response, that occurs during request processing.
  • RequestToViewNameTranslator: view pretreatment converter, used to get the Request of the viewName, will provide the name of the Request into the default view.
  • ViewResolver: A View resolver that resolves the View name into a View of type View.
  • FlashMapManager: for storage, retrieval, and managementFlashMapExample, where FlashMap is suitable for saving Flash properties, and Flash properties are used to solve the storage of parameters that cannot be passed during redirection.

Initialization Process

Compared to the complexity of parsing Spring’s IOC, AOP and other initialization processes, MVC is easier, and probably the easiest part of Spring source code parsing, so let’s start.

Now that we’ve covered the nine special beans in SpringMVC, we have a rough idea of what each of them does, and the SpringMVC initialization process is really all about one to one correspondence, so it’s not so much the MVC initialization as the initialization of the nine beans.

From zero to one, we still need to find the entry to the initialization process. Servlet initialization was described earlier in Servlet configuration, where XML is configured based on the XmlWebApplicationContext container. Code of the configuration is done based on AnnotationConfigWebApplicationContext containers to load; Here we will focus on parsing the initialization process based on code configuration.

I’m going to write a very simple test Demo,

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.scan("com.test.spring.mvc");
// context.register(MvcConfig.class);
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcherServlet", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/"); }}@RestController
public class TestController {

    @GetMapping("/test")
    public String test(a){
        return "test"; }}Copy the code

Through a good idea to configure Tomcat container, after the visit http://localhost:8080/test, see successful return to the test display on a page.

Let’s start by exploring the initialization process step by step.

Registered DispatcherServlet

First we need to create the IOC container AnnotationConfigWebApplicationContext, it is before the IOC resolution of AnnotationConfigApplicationContext Web version; Then we set up the IOC package scan path, which is mainly used to scan the Controller class we wrote.

We know that DispatcherServlet is an implementation subclass of Servlet, so before we look at it, let’s look at servlets.

Servlets are Java programs that run in a Web server, and their life cycle is as follows,

  1. When a Web container loads or first uses a Servlet, it creates a Servlet instance;
  2. Once instantiated, the container calls the init() method, which can only be called once per Servlet instance;
  3. After initialization, the Servlet remains in the Web container and responds to client requests through the service() method.
  4. To destroy, the destroy() method is called (only once), and the Servlet objects freed by the Web container are destroyed through the garbage collector when all requests being executed in the Service () method have completed or timed out.

Here we create a DispatcherServlet object and register it with the IOC container. Then we register the DispatcherServlet with the container’s Servlet and set two properties:

  • SetLoadOnStartup: Sets the loading order of DispatcherServlet. When the value is greater than or equal to 0, it means that the container loads and initializes the Servlet upon startup. The smaller the value is, the higher the loading priority is. Less than 0 or not set, it means that the container will not be loaded until the Servlet is selected.
  • AddMapping: Adds url path mapping. You can configure the URL path prefix of the project interface. This parameter is mandatory by default/.

You can see that these methods call the Servlet’s native API, and the real processing code is implemented in the Web container according to the Servlet’s canonical interface. The most important thing to focus on is the implementation of the Servlet native API in SpringMVC, namely the DispatcherServlet class, which is also the core of SpringMVC.

Initialize the Servlet

We already know that the init() method is called first after the Servlet is instantiated. However, if we look at the DispatcherServlet source and we don’t find this method, then the implementation of this method must be in one of its parent classes. Let’s first look at the source code of the top-level superclass interface Servlet,

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig(a);

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo(a);

    void destroy(a);
}
Copy the code

Find that init() is implemented by subclass GenericServlet,

public abstract class GenericServlet implements Servlet.ServletConfig.Serializable {...public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init(a) throws ServletException {}... }Copy the code

This calls a custom init() method that delegates to a subclass HttpServletBean.

    public final void init(a) throws ServletException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Initializing servlet '" + this.getServletName() + "'");
        }
	    //init-param sets the Bean property
        PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties);
        if(! pvs.isEmpty()) {try {
                // Encapsulate BeanWrapper in the IOC container
                BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
                ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext());
                bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment()));
                this.initBeanWrapper(bw);
                // Attribute injection
                bw.setPropertyValues(pvs, true);
            } catch (BeansException var4) {
                if (this.logger.isErrorEnabled()) {
                    this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4);
                }
                throwvar4; }}/ / initialization
        this.initServletBean();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully"); }}Copy the code

We focus on calling the initServletBean() method to initialize the Servlet. The implementation is done by the FrameworkServlet.

   protected final void initServletBean(a) throws ServletException {
        this.getServletContext().log("Initializing Spring FrameworkServlet '" + this.getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();
        try {
            this.webApplicationContext = this.initWebApplicationContext();
            // Empty implementation, subclass extension interface
            this.initFrameworkServlet();
        } catch (ServletException var5) {
            this.logger.error("Context initialization failed", var5);
            throw var5;
        } catch (RuntimeException var6) {
            this.logger.error("Context initialization failed", var6);
            throw var6;
        }
        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + this.getServletName() + "': initialization completed in " + elapsedTime + " ms"); }}Copy the code

Through the source code, we see the role is mainly to call initWebApplicationContext () method to initialize WebApplicationContext, And we know that the WebApplicationContext instance was created before registering the DispatcherServlet,

    protected WebApplicationContext initWebApplicationContext(a) {
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
        WebApplicationContext wac = null;
        if (this.webApplicationContext ! =null) {
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac;
                if(! cwac.isActive()) {if (cwac.getParent() == null) {
                        cwac.setParent(rootContext);
                    }
                    // Configure and refresh ApplicationContext
                    this.configureAndRefreshWebApplicationContext(cwac); }}}if (wac == null) {
            / / load ApplicationContext
            wac = this.findWebApplicationContext();
        }
        if (wac == null) {
            / / create the ApplicationContext
            wac = this.createWebApplicationContext(rootContext);
        }
        if (!this.refreshEventReceived) {
            this.onRefresh(wac);
        }
        if (this.publishContext) {
            // Cache to attributes
            String attrName = this.getServletContextAttributeName();
            this.getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + this.getServletName() + "' as ServletContext attribute with name [" + attrName + "]"); }}return wac;
    }
Copy the code

The process logic for initializing WebApplicationContext is as follows:

  1. WebApplicationContext exists, because has passed constructor injection, build parent-child relationship, container calls configureAndRefreshWebApplicationContext () to initialize the container (final call the refresh () method to complete).
  2. WebApplicationContext does not exist, try from the ServletContext attribute cache loading, if the load is less than, call the createWebApplicationContext create webApplicationContext instance () to the default, And to establish the relationships container, call configureAndRefreshWebApplicationContext () to initialize the container;
  3. Call the onRefresh() method to initialize the MVC component;
  4. Cache the webApplicationContext into the properties of the ServletContext.

This method completes the Servlet initialization of the DispatcherServlet and indicates that the Web container has been started.

Initialize related components

We know that DispatcherServlet is the core of SpringMVC, which encapsulates various components of MVC. Now let’s look at how we initialize MVC components in the onRefresh() method above.

The onRefresh() method is implemented under the DispatcherServlet subclass FrameworkServlet.

    protected void onRefresh(ApplicationContext context) {
        this.initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }
Copy the code

The implementation code here encapsulates the initialization methods for each component, in order:

  • Initializing multiple file upload
  • Initialization of locale parsing
  • The topic parser is initialized
  • Request mapping initialization
  • Request adapter initialization
  • Request exception resolution initialization
  • View preprocessor initialization
  • Initialize the view parser
  • The FlashMap management is initialized

If point in view of their respective initialization logic will find very simple, actually is the instantiation of nine key Bean, some of these components in the absence of configuration, will use the default configuration to analytical processing, and their respective role has also introduced the front, there is not one by one analysis of interconnected initialization method of source code.

Calling process

Now that the Web container has been successfully started, let’s think about how SpringMVC parses the request when the server receives it from the client.

First, going back to the Servlet lifecycle, we know that servlets will always exist in the Web container and then respond to client requests through the service() method, so let’s start with this entry point.

1. Start parsing

The service() method of the interface Servlet of the top parent class is overwritten by the subclass FrameworkServlet.

    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
        if(HttpMethod.PATCH ! = httpMethod && httpMethod ! =null) {
            super.service(request, response);
        } else {
            this.processRequest(request, response); }}Copy the code

When the request is not empty and the mode is not PATCH, the service() method of the parent HttpServlet class is called,

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304); }}}else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg); }}Copy the code

The doGet(), doPost(), doPut(), etc., are actually delivered to the FrameworkServlet, and processRequest() handles all request processing.

.protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response);
    }

    protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.processRequest(request, response); }...protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Is used to count the processing time of requests
        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;
        // Retain a snapshot of the request, locale, attributes, etc
        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = this.buildLocaleContext(request);
        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
        this.initContextHolders(request, localeContext, requestAttributes);
        try {
            // Request processing
            this.doService(request, response);
        } catch (IOException | ServletException var16) {
            failureCause = var16;
            throw var16;
        } catch (Throwable var17) {
            failureCause = var17;
            throw new NestedServletException("Request processing failed", var17);
        } finally {
            // Restore the original properties
            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
            if(requestAttributes ! =null) {
                requestAttributes.requestCompleted();
            }
            if (this.logger.isDebugEnabled()) {
                if(failureCause ! =null) {
                    this.logger.debug("Could not complete request", (Throwable)failureCause);
                } else if (asyncManager.isConcurrentHandlingStarted()) {
                    this.logger.debug("Leaving response open for concurrent processing");
                } else {
                    this.logger.debug("Successfully completed request"); }}// Publish request processing events
            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause); }}Copy the code

Analysis of source code know, here is mainly on the request before and after the preparation and event processing work, in order to ensure that the original attributes of the request before and after the same; The details are delegated to the doService() method of the DispatcherServlet subclass,

    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (this.logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            this.logger.debug("DispatcherServlet with name '" + this.getServletName() + "'" + resumed + " processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
        }
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration attrNames = request.getAttributeNames();
            label112:
            while(true) {
                String attrName;
                do {
                    if(! attrNames.hasMoreElements()) {break label112;
                    }
                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && ! attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
        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);
        }
        try {
            this.doDispatch(request, response);
        } finally {
            if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot ! =null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot); }}}Copy the code

The implementation logic is relatively simple, the main role is to prepare for the processing of the request, the MVC initialization of the relevant component configuration saved in the request of the attribute, so that the subsequent parsing work; More detailed parsing is done through the encapsulated doDispatch() method,

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;
                try {
                    // Multipart request check conversion
                    processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest ! = request;// Get the Handler for the request
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }
                    / / get HandlerAdapter
                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = "GET".equals(method);
                    if (isGet || "HEAD".equals(method)) {
                        //Last-Modified cache mechanism
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if (this.logger.isDebugEnabled()) {
                            this.logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                        }
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return; }}// preprocessing
                    if(! mappedHandler.applyPreHandle(processedRequest, response)) {return;
                    }
                    // Process the request
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }
                    // Apply the default view name
                    this.applyDefaultViewName(processedRequest, mv);
                    // post-processing
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }
                // Result processing
                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); }}finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if(mappedHandler ! =null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); }}else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest); }}}Copy the code

After layer upon layer of preparation, the parsing logic of the specific request is finally revealed, and the function of the related components initialized earlier is also reflected here. Next, the specific processing logic of the source code will be parsed separately.

2. Multi-part request conversion

Start will call checkMultipart () method to check whether you need the current request containing the file upload is converted into a multipart request MultipartHttpServletRequest, went in to see the source code,

    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
        if (this.multipartResolver ! =null && this.multipartResolver.isMultipart(request)) {
            if(WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) ! =null) {
                this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");
            } else if (this.hasMultipartException(request)) {
                this.logger.debug("Multipart resolution failed for current request before - skipping re-resolution for undisturbed error rendering");
            } else {
                try {
                    return this.multipartResolver.resolveMultipart(request);
                } catch (MultipartException var3) {
                    if (request.getAttribute("javax.servlet.error.exception") = =null) {
                        throwvar3; }}this.logger.debug("Multipart resolution failed for error dispatch", var3); }}return request;
    }
Copy the code

If the multipartResolver parser has not been configured, the check will be skipped. Otherwise, isMultipart() is called to determine whether the current request isMultipart. If so, Finally will through the MultipartResolver parser calls resolveMultipart () converts the current request MultipartHttpServletRequest, inspect its class diagram, You’ll see that it’s actually an extended subclass of HttpServletRequest; The source code for the transform processing in resolveMultipart() is also very complex, so you can delve deeper into it.

3. Obtain the Handler of the request

After checking the request, we call getHandler() to get the handler of the current request, which is the controller of the request path. Let’s look at how to find the handler.

    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings ! =null) {
            Iterator var2 = this.handlerMappings.iterator();
            while(var2.hasNext()) {
                HandlerMapping hm = (HandlerMapping)var2.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
                }
                HandlerExecutionChain handler = hm.getHandler(request);
                if(handler ! =null) {
                    returnhandler; }}}return null;
    }
Copy the code

Where handlerMappings is a List, two types of Url mappings are loaded at initialization:

  • BeanName BeanNameUrlHandlerMapping: matching for the path of the Controller, such as BeanName = “/ test”; Although such URL configurations are supported in SpringMVC, they are not used in actual development.
  • @ RequestMapping RequestMappingHandlerMapping: matching, including @ GetMapping, @ PostMapping annotation set path.

Here we are still in a normal way of setting RequestMappingHandlerMapping how to parse the url is matched to the Controller, we see the getHandler () source,

    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        // Get the corresponding HandlerMethod
        Object handler = this.getHandlerInternal(request);
        if (handler == null) {
            handler = this.getDefaultHandler();
        }
        if (handler == null) {
            return null;
        } else {
            if (handler instanceof String) {
                String handlerName = (String)handler;
                handler = this.obtainApplicationContext().getBean(handlerName);
            }
            // Encapsulate to the execution chain
            HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);
            // cross-domain processing
            if (CorsUtils.isCorsRequest(request)) {
                CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
                CorsConfiguration handlerConfig = this.getCorsConfiguration(handler, request); CorsConfiguration config = globalConfig ! =null ? globalConfig.combine(handlerConfig) : handlerConfig;
                executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);
            }
            returnexecutionChain; }}Copy the code

It is implemented in the Subclass AbstractHandlerMapping of HandlerMapping. GetHandlerInternal () is called to match the corresponding Controller and is encapsulated as HandlerMethod. Then call getHandlerExecutionChain() to wrap the current request and HandlerMethod into the execution chain, and add the matching HandlerInterceptor to the execution chain. Finally, determine whether the current request is a cross-domain request, and if so, process HandlerExecutionChain again.

In this paper, the processing mode of responsibility chain is used to reduce the coupling between the request object and the processor, and the request parsing can be easily extended and intercepted.

Let’s look at a subclass AbstractHandlerMethodMapping getHandlerInternal in the realization of a (),

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        // Intercepts a valid URL path
        String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Looking up handler method for path " + lookupPath);
        }
        // Get read lock
        this.mappingRegistry.acquireReadLock();
        HandlerMethod var4;
        try {
            // Find the HandlerMethod by path
            HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
            if (this.logger.isDebugEnabled()) {
                if(handlerMethod ! =null) {
                    this.logger.debug("Returning handler method [" + handlerMethod + "]");
                } else {
                    this.logger.debug("Did not find handler method for [" + lookupPath + "]"); }}// Get the Controller object from the container and encapsulate it as a HandlerMethodvar4 = handlerMethod ! =null ? handlerMethod.createWithResolvedBean() : null;
        } finally {
            // Release the read lock
            this.mappingRegistry.releaseReadLock();
        }
        return var4;
    }
Copy the code

We know that the read lock is shared, while the write lock is exclusive. It is mainly used to ensure that the change of the registered mapping in the container does not affect the consistency with the corresponding Controller. LookupHandlerMethod () = lookupHandlerMethod()

    @Nullable
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();
        // Get the registered path
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if(directPathMatches ! =null) {
            // Matches Controller and adds it to matches
            this.addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }
        if(! matches.isEmpty()) {// Sort HandlerMethodMapping
            Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));
            Collections.sort(matches, comparator);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "]." + matches);
            }
            AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);
            if (matches.size() > 1) {
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);
                // Matching more than one of the same handlers throws an exception
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "' : {" + m1 + "," + m2 + "}"); }}this.handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        } else {
            return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request); }}private void addMatchingMappings(Collection<T> mappings, List<AbstractHandlerMethodMapping<T>.Match> matches, HttpServletRequest request) {
        Iterator var4 = mappings.iterator();
        while(var4.hasNext()) {
            T mapping = var4.next();
            / / match Mapping
            T match = this.getMatchingMapping(mapping, request);
            if(match ! =null) {
                matches.add(new AbstractHandlerMethodMapping.Match(match, (HandlerMethod)this.mappingRegistry.getMappings().get(mapping))); }}}Copy the code

Here is basically complete URL path matching, in these methods to do a lot of preparation and matching processing, it seems that the implementation is very complex, but slowly Debug down, you will find that the main process of the whole logic is relatively simple, more detailed matching logic here is no longer in-depth.

If the Handler to load the current request does not exist, the server will return a 404 error.

4. Obtain HandlerAdapter

Once you find the Handler, you need to call getHandlerAdapter() to get the Handler’s HandlerAdapter.

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters ! =null) {
            Iterator var2 = this.handlerAdapters.iterator();
            while(var2.hasNext()) {
                HandlerAdapter ha = (HandlerAdapter)var2.next();
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Testing handler adapter [" + ha + "]");
                }
                if (ha.supports(handler)) {
                    returnha; }}}throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
Copy the code

The code is relatively simple. During initialization, handlerAdapters will load the following three handlerAdapters:

  • HttpRequestHandlerAdapter: interface adapter implementation HttpRequestHandler Handler, need to rewrite the handleRequest method.
  • SimpleControllerHandlerAdapter: adaptation to realize interface Controller Handler, needs to be rewritten handleRequest method, and returns ModelAndView.
  • @ RequestMapping RequestMappingHandlerAdapter: and RequestMappingHandlerMapping matching use, adaptation and annotation Handler.

The Supports () method is used to determine the instanceof type to select the appropriate HandlerAdapter for subsequent processing.

5.LastModified caching

The server will add the last-Modified attribute to the response header when the first request is successful. The value of the last-modified attribute is the Last update time of the server. When a second access is requested, the getLastModified() method is called to get the if-Modified-since attribute in the request header, and the checkNotModified() method is called to check whether the contents of the server have changed Since the attribute value. If there is no change, the 304 status code is responded (only the response header is returned, otherwise the content is responded).

6. Interceptor pre – and post-processing

Like Spring’s BeanPostProcessor, SpringMVC provides the HandlerInterceptor interceptor, which allows you to extend the processing of requests before and after the Handler actually processes the logic. Let’s take a look at the HandlerInterceptor source code.

public interface HandlerInterceptor {
    // make sure that you do it
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    // post-processing
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}// Complete the post-processing
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}}Copy the code

It provides three methods of interface implementation, development we can implement this interface to do some interception of Request processing.

7. Process requests

We have obtained the corresponding to the request in front of the Handler and do some preparation work before processing, and processing requests is truly through the handle () method to finish, here is called AbstractHandlerMethodAdapter the source code,

    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return this.handleInternal(request, response, (HandlerMethod)handler);
    }

Copy the code

Find the implementation or in subclasses RequestMappingHandlerAdapter handleInternal (),

    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        this.checkRequest(request);
        ModelAndView mav;
        // Session synchronization
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if(session ! =null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized(mutex) {
                    // Process logic
                    mav = this.invokeHandlerMethod(request, response, handlerMethod); }}else {
                // Process logic
                mav = this.invokeHandlerMethod(request, response, handlerMethod); }}else {
            // Process logic
            mav = this.invokeHandlerMethod(request, response, handlerMethod);
        }
        if(! response.containsHeader("Cache-Control")) {
		   // Session cache
            if (this.getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                this.applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                this.prepareResponse(response); }}return mav;
    }
Copy the code

We can see that Session synchronization and caching are done here, but the main focus is to call invokeHandlerMethod() to actually handle the request. If you look into the source code of this method, you can see that the parameters of the method are parsed and matched first. Finally, reflection is used to call the Controller classes and methods stored in the Handler we got earlier, which is our own implementation of the business logic code.

8. Exception handling

The above processing call returns a ModelAndView. If our service responds to a non-page view model such as JSON or XML, this mv is null. The ModelAndView is processed by calling the processDispatchResult() method,

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
        boolean errorView = false;
        if(exception ! =null) {
            if (exception instanceof ModelAndViewDefiningException) {
                this.logger.debug("ModelAndViewDefiningException encountered", exception);
                mv = ((ModelAndViewDefiningException)exception).getModelAndView();
            } else{ Object handler = mappedHandler ! =null ? mappedHandler.getHandler() : null;
                mv = this.processHandlerException(request, response, handler, exception); errorView = mv ! =null; }}if(mv ! =null && !mv.wasCleared()) {
            this.render(mv, request, response);
            if(errorView) { WebUtils.clearErrorRequestAttributes(request); }}else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + this.getServletName() + "': assuming HandlerAdapter completed request handling");
        }
        if(! WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {if(mappedHandler ! =null) {
                mappedHandler.triggerAfterCompletion(request, response, (Exception)null); }}}Copy the code

The result processing here is divided into two steps. The first is to call the processHandlerException() method to handle the exception that occurred in the logic that processed the request.

    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
        ModelAndView exMv = null;
        if (this.handlerExceptionResolvers ! =null) {
            Iterator var6 = this.handlerExceptionResolvers.iterator();
            while(var6.hasNext()) {
                HandlerExceptionResolver handlerExceptionResolver = (HandlerExceptionResolver)var6.next();
                exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
                if(exMv ! =null) {
                    break; }}}if(exMv ! =null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            } else {
                if(! exMv.hasView()) { String defaultViewName =this.getDefaultViewName(request);
                    if(defaultViewName ! =null) { exMv.setViewName(defaultViewName); }}if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
                }
                WebUtils.exposeErrorRequestAttributes(request, ex, this.getServletName());
                returnexMv; }}else {
            throwex; }}@Nullable
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
        if (this.shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]." + ex);
            }
            this.prepareResponse(ex, response);
            ModelAndView result = this.doResolveException(request, response, handler, ex);
            if(result ! =null) {
                this.logException(ex, request);
            }
            return result;
        } else {
            return null; }}Copy the code

We can see that the exception information, status code and so on will be written to the Response and returned to the client.

9. View rendering

If the ModelAndView returned as a result of the current request processing exists, the render() method is called to render the page,

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Locale locale = this.localeResolver ! =null ? this.localeResolver.resolveLocale(request) : request.getLocale();
        response.setLocale(locale);
        // Get the view name
        String viewName = mv.getViewName();
        View view;
        if(viewName ! =null) {
            // View resolution
            view = this.resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() + "' in servlet with name '" + this.getServletName() + "'"); }}else {
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a View object in servlet with name '" + this.getServletName() + "'"); }}if (this.logger.isDebugEnabled()) {
            this.logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'");
        }
        try {
            if(mv.getStatus() ! =null) {
                response.setStatus(mv.getStatus().value());
            }
            // View rendering
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception var8) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" + this.getServletName() + "'", var8);
            }
            throwvar8; }}Copy the code

We will first press the name of the returned View, and then use the ViewResolver to parse and obtain the corresponding View. Finally, we will call render() to render the page and return the JSTL syntax, EL expression or original Request attributes in the page.

At this point, the call flow processing in SpringMVC is complete.


To do one thing extremely well is talent!