SpringMVC源码剖析-SpringMVC初始化

Posted 墨家巨子@俏如来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringMVC源码剖析-SpringMVC初始化相关的知识,希望对你有一定的参考价值。

12月28号,给俏如来投个票可好

前言

SpringMVC是基于Spring功能之上添加的Web框架,是市场上最流行的MVC框架,Spring MVC 是基于 Servlet 功能实现的,通过 Servlet 接口的 DispatcherServlet 来封装其核心功能实现,通过将请求分派给处理程序,同时带有可配置的处理程序映射、视图解析、本地语言、主题解析以及上载文件支持。
在之前的《Spring源码剖析》系列文章中我们对Spring核心进行了分析,从这篇文章开始着手于SpringMVC源码分析。如果对你有所帮助,请使劲三连。

SpringMVC执行流程

SpringMVC执行流程几乎是在面试时面试官对SpringMVC部分的必问之题,下面是SpirngMVC的执行原理图

这个是请求在SpringMVC的执行流程

  1. DispatcherServlet:请求打过来由DispatcherServlet处理,它是 SpringMVC 中的前端控制器(中央控制器), 负责接收 Request 并将 Request 转发给对应的处理组件
  2. HandlerMapping:HandlerMapping 维护了 url 和 Controller(Handler)的 映 射关系 。 DispatcherServlet 接 收 请求, 然 后 从 HandlerMapping 查找处理请求的Controller(Handler),标注了@RequestMapping 的每个 method 都可以看成是一个 Handler,HandlerMapping 在请求到达之后, 它的作用便是找到请求相应的处理器 Handler 和 Interceptors。
  3. HandlerAdapter:SpringMVC通过HandlerAdapter对Handler进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。它的作用就是按照特定的规则去执行 Controller (Handler)
  4. Handler : Controller (Handler)负责处理请求,Controller 执行后并返回 ModelAndView 对象,其中包括了数据模型和逻辑视图,ModelAndView 是封装结果 视图的组件。Handler把结果返回给HandlerAdapter,HandlerAdapter把结果返回给DispatcherServlet前端控制器。
  5. ViewResolver:DispatcherServlet收到ModelAndView,调用视图解析器(ViewResolver)来解析HandlerAdapter传递的ModelAndView。Handler执行完成后返回的是逻辑视图,也就是视图名字,一个String ,还有一个Model就是数据模型,封装成ModelAndView。ViewResolver视图解析的作用就是根据视图名,把本地模板文件(比如:xx.jsp;xx.ftl)解析为View视图对象。View用来渲染视图,也就是负责把Handler返回的数据模型model,填充到模板(jsp;ftl)形成html格式的静态内容。
  6. 最后就是把生成的html通过response写给浏览器,浏览器进行html渲染展示。

ContextLoaderListener 初始化XmlWebApplicationContext 容器

脱离WEB环境使用Spring我们可以直接用 new ClasspathXmlApplicationContext(“配置文件.xml”)来启动,但是在Web环境中我们需要把Spring和web结合,在SpringMVC中使用的容器工厂是XmlWebApplicationContext,ContextLoaderListener 的作用就是通过实现ServletContextListener 监听ServletContext初始化,然后创建XmlWebApplicationContext 容器工厂并设置到ServletContext上下文对象中。

在SpringMVC中我们需要在web.xml做如下配置

<!--告诉ContextLoadContextLoaderListener ,Spring文件的位置 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<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></param-name>
        <param-value></param-value>
    </init-param>
</servlet>

<!--所有请求都会被springmvc拦截 -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<!--上下文监听器 -->
<listener>
   <listenerclass>
     org.springframework.web.context.ContextLoaderListener
   </listener-class>
</listener>
  • contextConfigLocation:通过context-param 配置contextConfigLocation ,值就是Spring的配置文件名,ContextLoaderListener会去加载这个配置文件。
  • DispatcherServlet:前端控制器,控制SpringMVC的请求处理过程
  • ContextLoaderListener :上下文监听器,负责在WEB容器启动时,自动装配ApplicationContext信息。

ContextLoaderListener实现了ServletContextListener ,它可以监听到ServletContext的contextInitialized初始化和contextDestroyed销毁事件。ServletContext是Servlet上下文对象,伴随着程序启动而创建,程序销毁而销毁,全局有效。我们也可以自定义ServletContextListener 的实现类来做我们自己的一些全局初始化工作。

ContextLoaderListener主要就是通过监听ServletContext的Initialized初始化,然后创建WebApplicationContext容器工厂,并添加到到ServletContext对象中。下面是ServletContextListener的源码

public interface ServletContextListener extends EventListener 

    public default void contextInitialized(ServletContextEvent sce) 
    

    public default void contextDestroyed(ServletContextEvent sce) 
    

下面是ContextLoaderListerner源码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener 

    /**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) 
	   //初始化容器
		initWebApplicationContext(event.getServletContext());
	


初始化容器,并设置到ServletContext ,见:ContextLoader#initWebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) 
	...省略...
		try 

			
			if (this.context == null) 
				//【重要】这里在创建WebApplicationContext
				this.context = createWebApplicationContext(servletContext);
			
			if (this.context instanceof ConfigurableWebApplicationContext) 
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				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 ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				
			
			...省略...
			//【重要】把WebApplicationContext放到servletContext
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			...省略...

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

ContextLoaderListener 调用 ContextLoader#initWebApplicationContext 器初始化容器对象,这里主要做了两个事情

  • 调用createWebApplicationContext创建上下文对象
  • 把WebApplicationContext设置到ServletContext中

跟进一下createWebApplicationContext方法,看一下是如何创建上下文对象的

   //【重要】加载ContextLoader.properties配置文件
   private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

   private static final Properties defaultStrategies;

	static 
		// Load default strategy implementations from properties file.
		// This is currently strictly internal and not meant to be customized
		// by application developers.
		try 
		   //【重要】加载配置文件ContextLoader.properties
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		
		catch (IOException ex) 
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		
	

	//【重要】 创建上下文对象
	protected WebApplicationContext createWebApplicationContext(ServletContext sc) 
		//【重要】这里在拿class
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) 
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		
		//使用BeanUtils.instantiateClass 根据class反射创建容器对象
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	

//找到容器对象的class
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 
		//【重要】从ContextLoader.properties加载class名
			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);
			
		
	

该方法主要就是从ContextLoader.properties加载容器对象的class和创建对象的过程

  • 先是通过ContextLoader的static静态代码块加载ContextLoader.properties配置得到一个Properties
  • 然后从Properties 中得到ContextClassName(使用的是XmlWebApplicationContext)返回
  • 然后通过BeanUtils.instantiateClass(contextClass)实例化对象
  • 最后把容器对象在设置到ServletContext

ContextLoadeer.properties中使用的是XmlWebApplicationContext ,配置文件内容如下

DispatcherServlet 初始化

首先来看一下DispatcherServlet的继承体系

DispatcherServlet是一个Servlet,拥有Servlet的生命周期,在Servlet初始化阶段会调用init方法,见其父类org.springframework.web.servlet.HttpServletBean#init

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

		// Set bean properties from init parameters.
		//设置初始化参数,比如:context-param
		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);
			//把servlet参数设置到BeanWrapper 对象中
			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.
		//【重要】初始化ServletBean
		initServletBean();

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

init方法中调用initServletBean初始化ServletBean,继续跟踪下去,代码来到org.springframework.web.servlet.FrameworkServlet#initServletBean

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 
		 //【重要】这里在初始化WebApplicationContext
			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");
		
	

FrameworkServlet#initServletBean方法中调用initWebApplicationContext初始化IOC容器对象,在ContextLoaderListener中已经创建了WebApplicationContext , 这里只是做初始化。见:org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext

protected WebApplicationContext initWebApplicationContext() 
		//【重要】从ServletContext中获取WebApplicationContext,也就是通过ContextLoadListener创建的
		// rootContext 根容器,是从ServletContext中拿到的容器对象
WebApplicationContext
		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);
					
					//【重要】刷新上下文,会走到ioc的 refresh();方法
					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) 
			//[重要]如果wac 为空,到这里还没有WebApplicationContext就会走
			//FrameworkServlet#createWebApplicationContext,创建一个XmlWebApplicationContext
			//然后执行 wac.setParent(parent); 形成一个父子容器 ,rootContext是针对SpringMVC的容器,wac是针对Spring的容器
			//最后会走容器的Refresh刷新方法刷新容器
			// 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.
			//[重要]初始化SpringMVC核心组件
			onRefresh(wac);
		

		if (this.publishContext) 
			//把context作为ServletContext中的属性
			// 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方法中从ServletContext中拿到WebApplicationContext作为rootContext根容器,然后会走configureAndRefreshWebApplicationContext方法创建一个新的WebApplicationContext作为子容器形成父子容器,最终调用容器的AbstractApplicationContext#refreshrefresh()刷新容器,这个在Spring源码分析中已经有说道。

刷新完成容器后,会调用 onRefresh(wac)方法; 见org.springframework.web.servlet.DispatcherServlet#onRefresh

/**
	 * This implementation calls @link #initStrategies.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) 
		initStrategies(context);
	

	/**
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) 
		//多文件上传的组件 
		initMultipartResolver(context); 
		//初始化本地语言环境 
		initLocaleResolver(context); 
		//初始化模板处理器 
		initThemeResolver(context); 
		//初始化handlerMapping 
		initHandlerMappings(context); 
		//初始化参数适配器 
		initHandlerAdapters(context); 
		//初始化异常拦截器 
		initHandlerExceptionResolvers(context); 
		//初始化视图预处理器 
		initRequestToViewNameTranslator(context); 
		//初始化视图转换器 
		initViewResolvers(context); 
		//FlashMap 管理器 
		initFlashMapManager(context);
	

initStrategies中初始化了SpringMVC最核心的九大组件

initMultipartResolver(context);

初始化文件上传的组件 ,MultipartResolver作为文件上传解析组件,如果开发中需要使用MultipartResolver需要在xml中配置<bean id="multipartResolver" class="org.Springframework.web.multipart.commons.CommonsMultipartResolver" /> , 这样请求中的multipart属性就会被处理。

private void initMultipartResolver(ApplicationContext context) 
		try 
		//从容器中查找Bean,如果在xml配置了这里就能获取到
			this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
			if (logger.isDebugEnabled()) 
				logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
			
		
		catch (NoSuchBeanDefinitionException ex) 
			// Default is no multipart resolver.
			this以上是关于SpringMVC源码剖析-SpringMVC初始化的主要内容,如果未能解决你的问题,请参考以下文章

Spring Mvc源码剖析

Spring Mvc源码剖析

Spring Mvc源码剖析

Spring Mvc源码剖析

SpringMVC源码剖析5:消息转换器HttpMessageConverter与@ResponseBody注解

从源码角度深度剖析 Spring MVC