【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析)

DispatcherServlet类相关的结构图

《【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析)》

 

DispatcherServlet的初始化程序

DispatcherServlet初始化了什么,可以在其initStrategies()方法中知晓,这个方法如下:

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

需要做的八件事情如下所述:

  • initMultipartResolver:初始化MultipartResolver,用于处理文件上传服务,如果有文件上传,那么就会将当前的HttpServletRequest包装成DefaultMultipartHttpServletRequest,并且将每个上传的内容封装成CommonsMultipartFile对象。需要在dispatcherServlet-servlet.xml中配置文件上传解析器。
  • initLocaleResolver:用于处理应用的国际化问题,本地化解析策略。
  • initThemeResolver:用于定义一个主题。
  • initHandlerMapping:用于定义请求映射关系。
  • initHandlerAdapters:用于根据Handler的类型定义不同的处理规则。
  • initHandlerExceptionResolvers:当Handler处理出错后,会通过此将错误日志记录在log文件中,默认实现类是SimpleMappingExceptionResolver。
  • initRequestToViewNameTranslators:将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式。
  • initViewResolvers:用于将View解析成页面。
  • initFlashMapManager:用于生成FlashMap管理器。

 

Spring MVC容器的初始化过程

首先,我们从web.xml中开始,在web.xml中我们首先配置的是contextLoaderListener,它的作用就是启动web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会自动执行它实现的contextInitialized()方法。这样就能够在客户端请求之前向ServletContext中添加任意的对象。

在ServletContextListener中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中

public void contextInitialized(ServletContextEvent event) {
		this.contextLoader = createContextLoader();
		if (this.contextLoader == null) {
			this.contextLoader = this;
		}
		this.contextLoader.initWebApplicationContext(event.getServletContext());
	}

这是ContextLoaderListener中的contextInitialized()方法,这里主要是用initWebApplicationContext()方法来初始化WebApplicationContext。这里涉及到一个常用类WebApplicationContext:它继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操作及属性。

这边的操作就非常类似通过编程的方式使用Spring时使用ClassPathXmlApplicationContext类提供的功能。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		servletContext.log("Initializing Spring root WebApplicationContext");
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);
			}
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			if (logger.isDebugEnabled()) {
				logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
						WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
			}
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
			}

			return this.context;
		}
		catch (RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
		catch (Error err) {
			logger.error("Context initialization failed", err);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
			throw err;
		}
	}

在initWebApplicationContext()方法中主要体现了WebApplicationContext实例的创建过程。首先,验证WebApplicationContext的存在性,通过查看ServletContext实例中是否有对应key的属性验证WebApplicationContext是否已经创建过实例。如果没有通过createWebApplicationContext()方法来创建实例,并存放至ServletContext中。

	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
		return wac;
	}

在createWebApplicationContext()方法中,通过BeanUtils.instanceClass()方法创建实例,而WebApplicationContext的实现类名称则通过determineContextClass()方法获得。

	protected Class<?> determineContextClass(ServletContext servletContext) {
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

determineContextClass()方法,通过defaultStrategies.getProperty()方法获得实现类的名称,而defaultStrategies是在ContextLoader类的静态代码块中赋值的。具体的途径,则是读取ContextLoader类的同目录下的ContextLoader.properties属性文件来确定的。

也就是说,在初始化的过程中,程序会首先读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个类通过反射进行实例的创建。

综合以上的代码,ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的contextInitialized()方法初始化WebApplicationContext实例,并放入到ServletContext中。由于在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。

 

diapatcherServlet的初始化过程

DispatcherServlet实现了Servlet接口的实现类。Servlet的生命周期分为3个阶段:初始化、运行和销毁。而其初始化阶段可分为:

  • Servlet容器加载Servlet类,把类的.class文件中的数据读到内存中;
  • Servlet容器中创建一个ServletConfig对象。该对象中包含了Servlet的初始化配置信息;
  • Servlet容器创建一个Servlet对象;
  • Servlet容器调用Servlet对象的init()方法进行初始化。

Servlet的初始化阶段会调用它的init()方法,DispatcherServlet也不例外,在它的父类HttpServletBean中找到了该方法

public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		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;
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

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

init()方法中先通过ServletConfigPropertiesValues()方法对Servlet初始化参数进行封装,然后再将这个Servlet转换成一个BeanWrapper对象,从而能够以spring的方式来对初始化参数的值进行注入。这些属性如contextConfigLocation、namespace等等。同时注册一个属性编辑器,一旦在属性注入的时候遇到Resource类型的属性就会使用ResourceEditor去解析。再留一个initBeanWrapper(bw)方法给子类覆盖,让子类处真正执行BeanWrapper的属性注入工作。但是HttpServletBean的子类FrameworkServlet和DispatcherServlet都没有覆盖其initBeanWrapper(bw)方法,所以创建的BeanWrapper对象没有任何作用。

程序接着往下走,运行到了initServletBean()方法。在之前,ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这里是对这个实例的进一步补充初始化。这个方法在HttpServletBean的子类FrameworkServlet中得到了重写。

注:ContextLoaderListener载入的是除DispatcherServlet之外的其他的上下文配置文件,而Spring MVC的配置文件是在DispatcherServlet中设置的。

	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 {
			this.webApplicationContext = initWebApplicationContext();
			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");
		}
	}

在initServletBean()方法中,设计了一个计时器来统计初始化的执行时间,还提供了一个initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑给了initWebApplicationContext()方法。

protected WebApplicationContext initWebApplicationContext() {
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					}
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// No context instance was injected at construction time -> see if one
			// has been registered in the servlet context. If one exists, it is assumed
			// that the parent context (if any) has already been set and that the
			// user has performed any initialization such as setting the context id
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		}

		if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			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;
	}

initWebApplicationContext()方法主要用于创建或刷新WebApplicationContext实例,并对Servlet功能所使用的变量进行初始化。它获得ContextLoaderListener中初始化的rootContext。再通过构造函数和Servlet的contextAttribute属性查找ServletContext来进行webApplicationContext实例的初始化,如果都不行,只能重新创建一个新的实例。最终都要执行configureAndRefreshWebApplicationContext()方法中的refresh()方法完成servlet中配置文件的加载和与rootContext的整合。

再下来,重点来了:

if (!this.refreshEventReceived) {
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		}

onRefresh(wac)方法是FrameworkServlet提供的模板方法,在其子类DispatcherServlet的onRefresh()方法中进行了重写。而在onRefresh()方法中调用了initStrategies()方法来完成初始化工作,初始化Spring MVC的9个组件。

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

 

具体的时序图

《【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析)》

《【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析)》

 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_38410730/article/details/79426673
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞