Spring Web MVC Source Code Analysis (1) --- Dispatcher Servlet

Keywords: Spring xml Java Attribute

I. overview

The design idea of Spring web MVC is to distribute requests to handlers around Dispatcher Servlet.  

Dispatcher Servlet's workflow for processing a request is as follows:

Dispatcher Servlet is a real Servlet (inherited from HttpServlet), so when building a Spring MVC program, you need to configure the requests Dispatcher Servlet needs to handle, such as in web.xml:

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

In the Web MVC framework, each Dispatcher Servlet has its own Web Application Context, which inherits all bean s defined under the root Web Application Context. A typical hierarchical structure is as follows:

    

When Dispatcher Servlet is initialized, Spring MVC will look for a configuration file named [servlet-name]-servlet.xml in the WEB-INF folder. This Spring configuration creates new beans or rewrites beans under Root Context, so we create the configuration file dispatcher-servlet.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context = "http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
    <!--Scanning and injection only@Controller and@ControllerAdvice-->
    <context:component-scan base-package="com.alex.learning"
        use-default-filters="false">
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation"
                               expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>
    <mvc:annotation-driven />
</beans>

      

II. Dispacher Servlet initialization

The class diagram structure of Dispacher Servlet is as follows:

Framework Servlet: The base class of Spring web framework, which provides functions integrated with Spring Application Context.

HttpServletBean: Inherited from HttpServlet, it's a simple extension, and initialization begins with this kind of overridden init method.

The initialization process is as follows:

The init method of HttpServletBean is the entry method of Dispatcher Servlet initialization:

    // Write the parameters in the configuration file into the bean property of the servlet and call the initialization method of the subclass
    @Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Initialize the necessary parameters into the Bean
		try {
			PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
			BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
			ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
			bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
			initBeanWrapper(bw);
			bw.setPropertyValues(pvs, true);
		}
		catch (BeansException ex) {
			logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
			throw ex;
		}

		// Framework Servlet initialization
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

The initServletBean method of Framework Servlet:

    // The Properties of the bean have been set up to create the Web Application Context for the Servlet
	@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) {
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
            // Initialize Web Application Context
			this.webApplicationContext = initWebApplicationContext();
			// Default empty implementation, rewriting subclasses
            initFrameworkServlet();
		}
		catch (ServletException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}
		catch (RuntimeException ex) {
			this.logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (this.logger.isInfoEnabled()) {
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		}
	}

As you can see, initialize initWeb Application Context () that calls the Framework Servlet primarily

    // Initialize Web Application Context
	protected WebApplicationContext initWebApplicationContext() {
        // Get the root Web Application Context for this web app
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;
       
		if (this.webApplicationContext != null) {
			// There is a Web Application Context injected when Servlet is created
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// Currently, context has not refreshed - > set parent context and application_context_id
					if (cwac.getParent() == null) {
                        // No parent context, set to rootContext
						cwac.setParent(rootContext);
					}
                    // Configure wac and call its refresh method
                    // This completes the initialization of the IOC container and the Application Context
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
	        // If it is not injected when the servlet is created, check to see if wac exists under the current servlet context.
            // If so, it means that initialization has been completed, with Parent context and contextId set
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// If the servlet context does not have wac, create a
            // CongureAndRefreshWeb Application Context (cwac) is also called inside this method.
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
            // To determine whether refresh has passed, call the onRefresh method of Dispacher Servlet
			onRefresh(wac);
		}
        
        // If you need to publish wac as an attribute of servletContext
		if (this.publishContext) {
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) {
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			}
		}

		return wac;
	}

The process of initialization of Application Context has been described in detail in Spring core source code analysis--IoC container initialization. After initialization of Web Application Context of the whole Web App, OnRefresh(wac), a key method of Dispacher Servlet, is called, which really starts the relevant initialization of Spring MVC.

    DispatcherServlet:

	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	// Initialize policy objects that all servlet s need to use
    // It may be overridden by subclasses
	protected void initStrategies(ApplicationContext context) {
        // Initialize getBean("multipartResolver")
        // Supporting file upload, spring mvc only provides a Commons Fileupload
		initMultipartResolver(context);
        // Initialize getBean("localeResolver")
        // Used to support internationalization
		initLocaleResolver(context);
        // Initialize getBean("themeResolver")
        // Dealing with the Theme of web Pages
		initThemeResolver(context);
        // Initialize List < Handler Mapping > 
        // Key processes, mapping requests to handlers and a series of pre-and post-processors
		initHandlerMappings(context);
        // Initialize List < Handler Adapter >
        // Help servlet call handler directly
		initHandlerAdapters(context);
        // Initialize List < Handler Exception Resolver >
        // Processing all unexpected exception s, you can specify modelandView
        // Standard exceptions can also be captured 
		initHandlerExceptionResolvers(context);
        // Initialize getBean("viewNameTranslator")
        // If there is no viewName, the logically matched viewName, such as localhost/register.html, is register.
        // Then the ViewResolver might become localhost/register.jsp
		initRequestToViewNameTranslator(context);
        // Initialize List < ViewResolver >
        // viewResolver resolves the real relationship between viewName and view, such as suffix=".jsp" configured in this example
		initViewResolvers(context);
        // Initialize getBean ("Flash Map Manager")
        // FlashMap for storing and retrieving input and output 
        // Usually attributes are passed to another request during redirection
		initFlashMapManager(context);
	}

The implementations of initMultipartResolver, initLocaleResolver, initThemeResolver and initRequestToViewNameTranslator are simple getBean("**"). If an exception is thrown, the default trategy is selected according to Dispatcher Servlet. properties.

So let's take a closer look at the initHandler Mappings (context) method and the initHandler Adapters (which are also key initialization methods for subsequent request processing).     

    // Initialize handlers Mappings used by this class 
    // If BeanFactory does not have Handler Mapping, we use BeanNameUrlHandler Mapping by default.
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all Handler Mappings for Application Context, including ancestor Context
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// Sort handler Mappings
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// A default Andler Mapping will be added later 
			}
		}

		// If initialization handler Mappings is empty, add a default Handler Mapping
		if (this.handlerMappings == null) {
            // Dispatcher Servlet has a Dispatcher Servlet. properties file in the same package, and is created from this file
            // Default Handler Mapping -- Bean NameUrlHandler Mapping and Default Annotation Handler Mapping
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

Dispatcher Servlet.properties are as follows:

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

InitHandler Adapters, initHandler Exception Resolvers, initViewResolvers and initHandler Mapping have the same logic.

At this point, the Web Application Context has been initialized.

Posted by claypots on Thu, 20 Dec 2018 09:00:05 -0800