Analysis of spring MVC implementation principle

Keywords: Spring

1, Introduction to spring MVC

Spring MVC is a lightweight web framework that implements the request driven type of Web MVC design pattern based on spring. It uses the idea of MVC architecture pattern to decouple the responsibilities of the web layer and manage the life cycle of the objects required by the application, which provides great convenience for simplifying daily development.

2, Spring MVC core components

Dispatcher Servlet: the central controller, which uniformly schedules the calls of other components, is the control center of the whole request response, and is essentially a Servlet;

Handler: business processor, which processes specific requests from clients and returns processing results. It usually exists in the form of various controllers;

HandlerMapping: processor mapper. The mapping relationship between the client request URL and the service processor. The corresponding service processor can be found according to the request URL;

HandlerAdapter: processor adapter, which is responsible for calling the specific methods of the business processor and returning the logical view ModelAndView object;

ViewResolver: View parser, which is responsible for parsing the view ModelAndView object returned by the business processor into JSP;

3, Spring MVC workflow

1. The client sends requests, and all requests are processed by the central processor DispatcherServlet;

2. The dispatcher servlet obtains the corresponding business processor Handler object according to the client request URL through the processor mapper Handler mapping;

3. DispatcherServlet calls HandlerAdapter processor adapter to inform HandlerAdapter which Handler to execute;

4. The HandlerAdapter calls the method of the specific Handler(Controller), obtains the returned result ModelAndView, and returns the result to the dispatcher servlet;

5. DispatcherServlet gives the ModelAndView to the viewrestrover view parser for parsing, and then returns the real view;

6. Dispatcher servlet fills the model data into the view;

7. DispatcherServlet responds the result to the user.

4, Spring MVC flow chart

 

 

5, Spring MVC source code analysis

5.1 spring MVC startup process

Spring MVC first needs to configure DispatcherServlet from web.xml, as follows:

<servlet>
     <servlet-name>springmvc</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
      <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springmvc.xml</param-value>
      </init-param>
</servlet>
<servlet-mapping>
     <servlet-name>springmvc</servlet-name>
     <url-pattern>/</url-pattern>
</servlet-mapping>

 

According to servlet related knowledge, all requests will be handled by DispatcherServlet, and DispatcherServlet will be created and the init method of DispatcherServlet initialization will be executed when the project is started.

The FrameworkServlet inherited by the dispatcher servlet and the HttpServletBean inherited by the FrameworkServlet implement the init method of the HttpServlet. In fact, the initServletBean method is executed, which is rewritten by the subclass FrameworkServlet,

The implementation logic of the initServletBean method rewritten by FrameworkServlet is as follows:

 1 protected final void initServletBean() throws ServletException {
 2         try {
 3             /** 1.Initialize the Spring Web container*/
 4             this.webApplicationContext = initWebApplicationContext();
 5             /** 2.Initialize the framework Servlet, empty method, and give it to the subclass extension*/
 6             initFrameworkServlet();
 7         }
 8         catch (ServletException | RuntimeException ex) {
 9             logger.error("Context initialization failed", ex);
10             throw ex;
11         }
12     }

 

protected WebApplicationContext initWebApplicationContext() {

        /** 1. Trying to get WebApplicationContext */
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(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);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        /** 2.If there is no WebApplicationContext currently, initialize and refresh the WebApplicationContext */
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                /** 3.WebApplicationContext After initialization and refresh, the onRefresh method is executed*/
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
        return wac;
    }

 

Use the createWebApplicationContext method to create the IOC container WebApplicationContext and start the refresh container. After the Spring container is started, execute the onRefresh method to refresh the Servlet. The refresh logic of the Spring container is no longer detailed. The onRefresh method is actually handed over to the Spring container

The subclass DispatcherServlet is implemented. The onRefresh method source code of DispatcherServlet is as follows:

/** DispatcherServlet onRefresh method */
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        /** Initialize processor mapper HandlerMapping */
        initHandlerMappings(context);
        /** Initialize processor adapter handlerAdapter */
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        /** Initializing the view resolver ViewResolver */
        initViewResolvers(context);
        initFlashMapManager(context);
    }

It can be seen that onRefresh method is mainly used to initialize related components, such as initializing business processor mapper HandlerMapping, processor adapter HandlerAdapter, view parser ViewResolver, etc. here, we focus on the initialization of HandlerMapping and HandlerAdapter.

5.1.1 processor mapper initialization

First look at the initialization of the processor mapper. The method is initHandlerMapping(ApplicationContext context) of the dispatcher servlet. The source code is as follows:

 1 /** Initialize processor mapper */
 2     private void initHandlerMappings(ApplicationContext context) {
 3         this.handlerMappings = null;
 4         /** 1.First try to get all the HandlerMapping s from the Spring container*/
 5         if (this.detectAllHandlerMappings) {
 6             Map<String, HandlerMapping> matchingBeans =
 7                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
 8             if (!matchingBeans.isEmpty()) {
 9                 this.handlerMappings = new ArrayList<>(matchingBeans.values());
10                 AnnotationAwareOrderComparator.sort(this.handlerMappings);
11             }
12         }
13         else {
14             try {
15                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
16                 this.handlerMappings = Collections.singletonList(hm);
17             }
18             catch (NoSuchBeanDefinitionException ex) {
19             }
20         }
21 
22         /** 2.If there is no HandlerMapping in the Spring container, initialize the default HandlerMapping*/
23         if (this.handlerMappings == null) {
24             // Initialize default HandlerMapping
25             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
26         }
27     }
28 
29     /** Get default policy */
30     protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
31         String key = strategyInterface.getName();
32         /** 1.Get default policy from configuration file
33          *  The configuration file is DispatcherServlet.properties
34          *  key Full path for policy class
35          * */
36         String value = defaultStrategies.getProperty(key);
37         if (value != null) {
38             String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
39             List<T> strategies = new ArrayList<>(classNames.length);
40             for (String className : classNames) {
41                 try {
42                     /** 2.Reflection initializes all policy instances*/
43                     Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
44                     Object strategy = createDefaultStrategy(context, clazz);
45                     strategies.add((T) strategy);
46                 }
47                 catch (ClassNotFoundException ex) {
48                 }
49                 catch (LinkageError err) {
50                 }
51             }
52             return strategies;
53         }
54         else {
55             return new LinkedList<>();
56         }
57     }

 

First, try to get all HandlerMapping bean s from the Spring container. If they do not exist, load the default processor mapper. The getDefaultStrategies method is to load the default configuration from the configuration file DispatcherServlet.properties. The content of the configuration file is as follows:

 1 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
 2 
 3 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
 4 
 5 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
 6     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
 7     org.springframework.web.servlet.function.support.RouterFunctionMapping
 8 
 9 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
10     org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
11     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
12     org.springframework.web.servlet.function.support.HandlerFunctionAdapter
13 
14 
15 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
16     org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
17     org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
18 
19 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
20 
21 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
22 
23 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 

It can be found that the default HandlerMapping is BeanNameUrlHandlerMapping, RequestMappingHandlerMapping and RouterFunctionMapping. Taking RequestMappingHandlerMapping as an example, InitializingBean is implemented,

Therefore, after initialization, the afterPropertiesSet method will be executed. This method initializes the property and calls the afterPropertiesSet method of the parent class AbstractHandlerMapping. This method also executes the initHandlerMethods method and the method of AbstractHandlerMapping

The initHandlerMethods method is essentially a method of initializing method mapping. The logic is as follows:

/** Initialize path and method mapping */
    protected void initHandlerMethods() {

        /** 1.Traverse all bean s in the Spring container */
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                /** 2.Process all subsequent bean s */
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            /** 1.Gets the Class object of the bean */
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
        }
        /** 2.Call the isHandler method to determine whether the bean is a business processor*/
        if (beanType != null && isHandler(beanType)) {
            /** 2.Find mapping method in business processor */
            detectHandlerMethods(beanName);
        }
    }

    /** Determine whether the bean is a business processor (modified by Controller or RequestMapping annotations)*/
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

 

Traverse all beans in the Spring container to determine whether the bean contains @ Controller or @ RequestMapping annotation. If so, it is the business processor, and execute the detectHandlerMethods method. The logic of the method is as follows:

protected void detectHandlerMethods(Object handler) {
        /** 1.Get processor Class object */
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            /** 2.Find all mapping method sets and store them in map < method, requestmappinginfo >
             *  RequestMappingInfo Method mapping relation class */
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                /** Register the processor, method and mapping relationship, and cache them in the Map of the MappingRegistry instance */
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

 

First, find all mapping methods from the business processor and package them into mapping relationship instances, and then register the processor, method and mapping relationship instances into the MappingRegistry instance.

5.1.2 processor adapter initialization

The initialization process of the processor adapter HandlerAdapter is the same as that of the processor mapper HandlerMapping. Finally, the initialization of the default adapter RequestMappingHandlerAdapter is implemented, and finally the afterPropertiesSet method is rewritten.

 

5.2. Spring MVC workflow

 

 

------------Restore content start------------

1, Introduction to spring MVC

Spring MVC is a lightweight web framework that implements the request driven type of Web MVC design pattern based on spring. It uses the idea of MVC architecture pattern to decouple the responsibilities of the web layer and manage the life cycle of the objects required by the application, which provides great convenience for simplifying daily development.

2, Spring MVC core components

Dispatcher Servlet: the central controller, which uniformly schedules the calls of other components, is the control center of the whole request response, and is essentially a Servlet;

Handler: business processor, which processes specific requests from clients and returns processing results. It usually exists in the form of various controllers;

HandlerMapping: processor mapper. The mapping relationship between the client request URL and the service processor. The corresponding service processor can be found according to the request URL;

HandlerAdapter: processor adapter, which is responsible for calling the specific methods of the business processor and returning the logical view ModelAndView object;

ViewResolver: View parser, which is responsible for parsing the view ModelAndView object returned by the business processor into JSP;

3, Spring MVC workflow

1. The client sends requests, and all requests are processed by the central processor DispatcherServlet;

2. The dispatcher servlet obtains the corresponding business processor Handler object according to the client request URL through the processor mapper Handler mapping;

3. DispatcherServlet calls HandlerAdapter processor adapter to inform HandlerAdapter which Handler to execute;

4. The HandlerAdapter calls the method of the specific Handler(Controller), obtains the returned result ModelAndView, and returns the result to the dispatcher servlet;

5. DispatcherServlet gives the ModelAndView to the viewrestrover view parser for parsing, and then returns the real view;

6. Dispatcher servlet fills the model data into the view;

7. DispatcherServlet responds the result to the user.

4, Spring MVC flow chart

 

 

5, Spring MVC source code analysis

5.1 spring MVC startup process

Spring MVC first needs to configure DispatcherServlet from web.xml, as follows:

<servlet>
     <servlet-name>springmvc</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
      <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springmvc.xml</param-value>
      </init-param>
</servlet>
<servlet-mapping>
     <servlet-name>springmvc</servlet-name>
     <url-pattern>/</url-pattern>
</servlet-mapping>

 

According to servlet related knowledge, all requests will be handled by DispatcherServlet, and DispatcherServlet will be created and the init method of DispatcherServlet initialization will be executed when the project is started.

The FrameworkServlet inherited by the dispatcher servlet and the HttpServletBean inherited by the FrameworkServlet implement the init method of the HttpServlet. In fact, the initServletBean method is executed, which is rewritten by the subclass FrameworkServlet,

The implementation logic of the initServletBean method rewritten by FrameworkServlet is as follows:

 1 protected final void initServletBean() throws ServletException {
 2         try {
 3             /** 1.Initialize the Spring Web container*/
 4             this.webApplicationContext = initWebApplicationContext();
 5             /** 2.Initialize the framework Servlet, empty method, and give it to the subclass extension*/
 6             initFrameworkServlet();
 7         }
 8         catch (ServletException | RuntimeException ex) {
 9             logger.error("Context initialization failed", ex);
10             throw ex;
11         }
12     }

 

protected WebApplicationContext initWebApplicationContext() {

        /** 1. Trying to get WebApplicationContext */
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(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);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        /** 2.If there is no WebApplicationContext currently, initialize and refresh the WebApplicationContext */
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                /** 3.WebApplicationContext After initialization and refresh, the onRefresh method is executed*/
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
        return wac;
    }

 

Use the createWebApplicationContext method to create the IOC container WebApplicationContext and start the refresh container. After the Spring container is started, execute the onRefresh method to refresh the Servlet. The refresh logic of the Spring container is no longer detailed. The onRefresh method is actually handed over to the Spring container

The subclass DispatcherServlet is implemented. The onRefresh method source code of DispatcherServlet is as follows:

/** DispatcherServlet onRefresh method */
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        /** Initialize processor mapper HandlerMapping */
        initHandlerMappings(context);
        /** Initialize processor adapter handlerAdapter */
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        /** Initializing the view resolver ViewResolver */
        initViewResolvers(context);
        initFlashMapManager(context);
    }

It can be seen that onRefresh method is mainly used to initialize related components, such as initializing business processor mapper HandlerMapping, processor adapter HandlerAdapter, view parser ViewResolver, etc. here, we focus on the initialization of HandlerMapping and HandlerAdapter.

5.1.1 processor mapper initialization

First look at the initialization of the processor mapper. The method is initHandlerMapping(ApplicationContext context) of the dispatcher servlet. The source code is as follows:

 1 /** Initialize processor mapper */
 2     private void initHandlerMappings(ApplicationContext context) {
 3         this.handlerMappings = null;
 4         /** 1.First try to get all the HandlerMapping s from the Spring container*/
 5         if (this.detectAllHandlerMappings) {
 6             Map<String, HandlerMapping> matchingBeans =
 7                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
 8             if (!matchingBeans.isEmpty()) {
 9                 this.handlerMappings = new ArrayList<>(matchingBeans.values());
10                 AnnotationAwareOrderComparator.sort(this.handlerMappings);
11             }
12         }
13         else {
14             try {
15                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
16                 this.handlerMappings = Collections.singletonList(hm);
17             }
18             catch (NoSuchBeanDefinitionException ex) {
19             }
20         }
21 
22         /** 2.If there is no HandlerMapping in the Spring container, initialize the default HandlerMapping*/
23         if (this.handlerMappings == null) {
24             // Initialize default HandlerMapping
25             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
26         }
27     }
28 
29     /** Get default policy */
30     protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
31         String key = strategyInterface.getName();
32         /** 1.Get default policy from configuration file
33          *  The configuration file is DispatcherServlet.properties
34          *  key Full path for policy class
35          * */
36         String value = defaultStrategies.getProperty(key);
37         if (value != null) {
38             String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
39             List<T> strategies = new ArrayList<>(classNames.length);
40             for (String className : classNames) {
41                 try {
42                     /** 2.Reflection initializes all policy instances*/
43                     Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
44                     Object strategy = createDefaultStrategy(context, clazz);
45                     strategies.add((T) strategy);
46                 }
47                 catch (ClassNotFoundException ex) {
48                 }
49                 catch (LinkageError err) {
50                 }
51             }
52             return strategies;
53         }
54         else {
55             return new LinkedList<>();
56         }
57     }

 

First, try to get all HandlerMapping bean s from the Spring container. If they do not exist, load the default processor mapper. The getDefaultStrategies method is to load the default configuration from the configuration file DispatcherServlet.properties. The content of the configuration file is as follows:

 1 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
 2 
 3 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
 4 
 5 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
 6     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
 7     org.springframework.web.servlet.function.support.RouterFunctionMapping
 8 
 9 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
10     org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
11     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
12     org.springframework.web.servlet.function.support.HandlerFunctionAdapter
13 
14 
15 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
16     org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
17     org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
18 
19 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
20 
21 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
22 
23 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 

It can be found that the default HandlerMapping is BeanNameUrlHandlerMapping, RequestMappingHandlerMapping and RouterFunctionMapping. Taking RequestMappingHandlerMapping as an example, InitializingBean is implemented,

Therefore, after initialization, the afterPropertiesSet method will be executed. This method initializes the property and calls the afterPropertiesSet method of the parent class AbstractHandlerMapping. This method also executes the initHandlerMethods method and the method of AbstractHandlerMapping

The initHandlerMethods method is essentially a method of initializing method mapping. The logic is as follows:

/** Initialize path and method mapping */
    protected void initHandlerMethods() {

        /** 1.Traverse all bean s in the Spring container */
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                /** 2.Process all subsequent bean s */
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            /** 1.Gets the Class object of the bean */
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
        }
        /** 2.Call the isHandler method to determine whether the bean is a business processor*/
        if (beanType != null && isHandler(beanType)) {
            /** 2.Find mapping method in business processor */
            detectHandlerMethods(beanName);
        }
    }

    /** Determine whether the bean is a business processor (modified by Controller or RequestMapping annotations)*/
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

 

Traverse all beans in the Spring container to determine whether the bean contains @ Controller or @ RequestMapping annotation. If so, it is the business processor, and execute the detectHandlerMethods method. The logic of the method is as follows:

protected void detectHandlerMethods(Object handler) {
        /** 1.Get processor Class object */
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            /** 2.Find all mapping method sets and store them in map < method, requestmappinginfo >
             *  RequestMappingInfo Method mapping relation class */
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                /** Register the processor, method and mapping relationship, and cache them in the Map of the MappingRegistry instance */
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

 

First, find all mapping methods from the business processor and package them into mapping relationship instances, and then register the processor, method and mapping relationship instances into the MappingRegistry instance.

5.1.2 processor adapter initialization

The initialization process of the processor adapter HandlerAdapter is the same as that of the processor mapper HandlerMapping. Finally, the initialization of the default adapter RequestMappingHandlerAdapter is implemented, and finally the afterPropertiesSet method is rewritten.

 

5.2. Spring MVC workflow

 

 

------------End of recovery------------

------------Restore content start------------

1, Introduction to spring MVC

Spring MVC is a lightweight web framework that implements the request driven type of Web MVC design pattern based on spring. It uses the idea of MVC architecture pattern to decouple the responsibilities of the web layer and manage the life cycle of the objects required by the application, which provides great convenience for simplifying daily development.

2, Spring MVC core components

Dispatcher Servlet: the central controller, which uniformly schedules the calls of other components, is the control center of the whole request response, and is essentially a Servlet;

Handler: business processor, which processes specific requests from clients and returns processing results. It usually exists in the form of various controllers;

HandlerMapping: processor mapper. The mapping relationship between the client request URL and the service processor. The corresponding service processor can be found according to the request URL;

HandlerAdapter: processor adapter, which is responsible for calling the specific methods of the business processor and returning the logical view ModelAndView object;

ViewResolver: View parser, which is responsible for parsing the view ModelAndView object returned by the business processor into JSP;

3, Spring MVC workflow

1. The client sends requests, and all requests are processed by the central processor DispatcherServlet;

2. The dispatcher servlet obtains the corresponding business processor Handler object according to the client request URL through the processor mapper Handler mapping;

3. DispatcherServlet calls HandlerAdapter processor adapter to inform HandlerAdapter which Handler to execute;

4. The HandlerAdapter calls the method of the specific Handler(Controller), obtains the returned result ModelAndView, and returns the result to the dispatcher servlet;

5. DispatcherServlet gives the ModelAndView to the viewrestrover view parser for parsing, and then returns the real view;

6. Dispatcher servlet fills the model data into the view;

7. DispatcherServlet responds the result to the user.

4, Spring MVC flow chart

 

 

5, Spring MVC source code analysis

5.1 spring MVC startup process

Spring MVC first needs to configure DispatcherServlet from web.xml, as follows:

<servlet>
     <servlet-name>springmvc</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
      <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:springmvc.xml</param-value>
      </init-param>
</servlet>
<servlet-mapping>
     <servlet-name>springmvc</servlet-name>
     <url-pattern>/</url-pattern>
</servlet-mapping>

 

According to servlet related knowledge, all requests will be handled by DispatcherServlet, and DispatcherServlet will be created and the init method of DispatcherServlet initialization will be executed when the project is started.

The FrameworkServlet inherited by the dispatcher servlet and the HttpServletBean inherited by the FrameworkServlet implement the init method of the HttpServlet. In fact, the initServletBean method is executed, which is rewritten by the subclass FrameworkServlet,

The implementation logic of the initServletBean method rewritten by FrameworkServlet is as follows:

 1 protected final void initServletBean() throws ServletException {
 2         try {
 3             /** 1.Initialize the Spring Web container*/
 4             this.webApplicationContext = initWebApplicationContext();
 5             /** 2.Initialize the framework Servlet, empty method, and give it to the subclass extension*/
 6             initFrameworkServlet();
 7         }
 8         catch (ServletException | RuntimeException ex) {
 9             logger.error("Context initialization failed", ex);
10             throw ex;
11         }
12     }

 

protected WebApplicationContext initWebApplicationContext() {

        /** 1. Trying to get WebApplicationContext */
        WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(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);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
        if (wac == null) {
            wac = findWebApplicationContext();
        }
        /** 2.If there is no WebApplicationContext currently, initialize and refresh the WebApplicationContext */
        if (wac == null) {
            wac = createWebApplicationContext(rootContext);
        }

        if (!this.refreshEventReceived) {
            synchronized (this.onRefreshMonitor) {
                /** 3.WebApplicationContext After initialization and refresh, the onRefresh method is executed*/
                onRefresh(wac);
            }
        }

        if (this.publishContext) {
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
        return wac;
    }

 

Use the createWebApplicationContext method to create the IOC container WebApplicationContext and start the refresh container. After the Spring container is started, execute the onRefresh method to refresh the Servlet. The refresh logic of the Spring container is no longer detailed. The onRefresh method is actually handed over to the Spring container

The subclass DispatcherServlet is implemented. The onRefresh method source code of DispatcherServlet is as follows:

/** DispatcherServlet onRefresh method */
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }

    protected void initStrategies(ApplicationContext context) {
        initMultipartResolver(context);
        initLocaleResolver(context);
        initThemeResolver(context);
        /** Initialize processor mapper HandlerMapping */
        initHandlerMappings(context);
        /** Initialize processor adapter handlerAdapter */
        initHandlerAdapters(context);
        initHandlerExceptionResolvers(context);
        initRequestToViewNameTranslator(context);
        /** Initializing the view resolver ViewResolver */
        initViewResolvers(context);
        initFlashMapManager(context);
    }

It can be seen that onRefresh method is mainly used to initialize related components, such as initializing business processor mapper HandlerMapping, processor adapter HandlerAdapter, view parser ViewResolver, etc. here, we focus on the initialization of HandlerMapping and HandlerAdapter.

5.1.1 processor mapper initialization

First look at the initialization of the processor mapper. The method is initHandlerMapping(ApplicationContext context) of the dispatcher servlet. The source code is as follows:

 1 /** Initialize processor mapper */
 2     private void initHandlerMappings(ApplicationContext context) {
 3         this.handlerMappings = null;
 4         /** 1.First try to get all the HandlerMapping s from the Spring container*/
 5         if (this.detectAllHandlerMappings) {
 6             Map<String, HandlerMapping> matchingBeans =
 7                     BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
 8             if (!matchingBeans.isEmpty()) {
 9                 this.handlerMappings = new ArrayList<>(matchingBeans.values());
10                 AnnotationAwareOrderComparator.sort(this.handlerMappings);
11             }
12         }
13         else {
14             try {
15                 HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
16                 this.handlerMappings = Collections.singletonList(hm);
17             }
18             catch (NoSuchBeanDefinitionException ex) {
19             }
20         }
21 
22         /** 2.If there is no HandlerMapping in the Spring container, initialize the default HandlerMapping*/
23         if (this.handlerMappings == null) {
24             // Initialize default HandlerMapping
25             this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
26         }
27     }
28 
29     /** Get default policy */
30     protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
31         String key = strategyInterface.getName();
32         /** 1.Get default policy from configuration file
33          *  The configuration file is DispatcherServlet.properties
34          *  key Full path for policy class
35          * */
36         String value = defaultStrategies.getProperty(key);
37         if (value != null) {
38             String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
39             List<T> strategies = new ArrayList<>(classNames.length);
40             for (String className : classNames) {
41                 try {
42                     /** 2.Reflection initializes all policy instances*/
43                     Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
44                     Object strategy = createDefaultStrategy(context, clazz);
45                     strategies.add((T) strategy);
46                 }
47                 catch (ClassNotFoundException ex) {
48                 }
49                 catch (LinkageError err) {
50                 }
51             }
52             return strategies;
53         }
54         else {
55             return new LinkedList<>();
56         }
57     }

 

First, try to get all HandlerMapping bean s from the Spring container. If they do not exist, load the default processor mapper. The getDefaultStrategies method is to load the default configuration from the configuration file DispatcherServlet.properties. The content of the configuration file is as follows:

 1 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
 2 
 3 org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
 4 
 5 org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
 6     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
 7     org.springframework.web.servlet.function.support.RouterFunctionMapping
 8 
 9 org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
10     org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
11     org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
12     org.springframework.web.servlet.function.support.HandlerFunctionAdapter
13 
14 
15 org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
16     org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
17     org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
18 
19 org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
20 
21 org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
22 
23 org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

 

It can be found that the default HandlerMapping is BeanNameUrlHandlerMapping, RequestMappingHandlerMapping and RouterFunctionMapping. Taking RequestMappingHandlerMapping as an example, InitializingBean is implemented,

Therefore, after initialization, the afterPropertiesSet method will be executed. This method initializes the property and calls the afterPropertiesSet method of the parent class AbstractHandlerMapping. This method also executes the initHandlerMethods method and the method of AbstractHandlerMapping

The initHandlerMethods method is essentially a method of initializing method mapping. The logic is as follows:

/** Initialize path and method mapping */
    protected void initHandlerMethods() {

        /** 1.Traverse all bean s in the Spring container */
        for (String beanName : getCandidateBeanNames()) {
            if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                /** 2.Process all subsequent bean s */
                processCandidateBean(beanName);
            }
        }
        handlerMethodsInitialized(getHandlerMethods());
    }

    protected void processCandidateBean(String beanName) {
        Class<?> beanType = null;
        try {
            /** 1.Gets the Class object of the bean */
            beanType = obtainApplicationContext().getType(beanName);
        }
        catch (Throwable ex) {
        }
        /** 2.Call the isHandler method to determine whether the bean is a business processor*/
        if (beanType != null && isHandler(beanType)) {
            /** 2.Find mapping method in business processor */
            detectHandlerMethods(beanName);
        }
    }

    /** Determine whether the bean is a business processor (modified by Controller or RequestMapping annotations)*/
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

 

Traverse all beans in the Spring container to determine whether the bean contains @ Controller or @ RequestMapping annotation. If so, it is the business processor, and execute the detectHandlerMethods method. The logic of the method is as follows:

protected void detectHandlerMethods(Object handler) {
        /** 1.Get processor Class object */
        Class<?> handlerType = (handler instanceof String ?
                obtainApplicationContext().getType((String) handler) : handler.getClass());

        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            /** 2.Find all mapping method sets and store them in map < method, requestmappinginfo >
             *  RequestMappingInfo Method mapping relation class */
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            return getMappingForMethod(method, userType);
                        }
                        catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });
            methods.forEach((method, mapping) -> {
                Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
                /** Register the processor, method and mapping relationship, and cache them in the Map of the MappingRegistry instance */
                registerHandlerMethod(handler, invocableMethod, mapping);
            });
        }
    }

 

First, find all mapping methods from the business processor and package them into mapping relationship instances, and then register the processor, method and mapping relationship instances into the MappingRegistry instance.

5.1.2 processor adapter initialization

The initialization process of the processor adapter HandlerAdapter is the same as that of the processor mapper HandlerMapping. Finally, the initialization of the default adapter RequestMappingHandlerAdapter is implemented, and finally the afterPropertiesSet method is rewritten.  

5.2. Spring MVC workflow

  The Servlet workflow is actually to execute the service method after receiving the client request, so the request processing entry of spring MVC is the service method of DispatcherServlet, and the service method of DispatcherServlet is the service method of the parent FrameworkServlet.

This method also executes the processRequest method of the FrameworkServlet, and finally calls the doService method of the subclass. The doService method of the dispatcher servlet is the core method for processing business logic. The source code is as follows:

/** DispatcherServlet Business processing method */
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        /** 1.Request parameter snapshot to cache request parameters */
        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));
                }
            }
        }

        /** 2.Request parameter add configuration */
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, 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 {
            /** 3.Execute allocation request processing */
            doDispatch(request, response);
        }
        finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
                if (attributesSnapshot != null) {
                    restoreAttributesAfterInclude(request, attributesSnapshot);
                }
            }
        }
    }

 

First, parameter processing is done, then the doDispatch method is used to allocate the request. The logic is as follows:

 

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;
            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                /** 1.Query the specific service processor from HandlerMapping according to the request */
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                /** 2.Query the corresponding service processor adapter according to the service processor */
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                /** 3.Call the handle method of the processor adapter to handle the specific business logic */
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                //...
            }
            /** 4.Processing request execution results */
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            //...
        } finally {
            //...
        }
    }

The core logic is clearer, first querying the corresponding business processor from the processor mapper, then finding the processor adapter according to the business processor, then calling the adapter's handle method to handle the business, and finally executing the processDispatchResult method to process the request processing result.

getHandler logic is to find the matching processor from the collection handlerMappings;

getHandlerAdapter is to find the corresponding adapter from the collection handlerAdapters;

The handle method is the method of executing the corresponding processor through the reflection mechanism;

processDispatchResult encapsulates the execution result into a ModelAndView object;  

 

Posted by Alith7 on Sun, 28 Nov 2021 22:42:17 -0800