spring-mvc源码-上下文的初始化

 

1、总览

目录

1、总览

2、根上下文初始化

3、Spring MVC容器(子上下文)的初始化

4、Spring MVC 上下文初始化流程图


   spring-mvc项目里一般会有两个上下文,一个是根上下文(Root WebApplicationContext),一个是spring mvc上下文(Servlet WebApplicationContext,可以有多个),那么为什么要存在这两种上下文,可以看下官方的解释。

DispatcherServlet expects a WebApplicationContext (an extension of a plain ApplicationContext) for its own configuration. WebApplicationContext has a link to the ServletContext and the Servlet with which it is associated. It is also bound to the ServletContext such that applications can use static methods on RequestContextUtils to look up theWebApplicationContext if they need access to it.

For many applications, having a single WebApplicationContext is simple and suffices. It is also possible to have a context hierarchy where one root WebApplicationContext is shared across multiple DispatcherServlet (or other Servlet) instances, each with its own child WebApplicationContext configuration.

The root WebApplicationContext typically contains infrastructure beans, such as data repositories and business services that need to be shared across multiple Servlet instances. Those beans are effectively inherited and can be overridden (that is, re-declared) in the Servlet-specific child WebApplicationContext, which typically contains beans local to the given Servlet.

 

大概意思是:

DispatcherServlet需要一个上下文用于其自身配置。这个上下文具有到ServletContext及其关联的Servlet的链接。它也绑定到ServletContext,这样应用程序就可以使用RequestContextUtils的静态方法访问这个上下文。

对于许多应用程序来说,拥有一个上下文是简单和足够的。但也可以有一个层次结构的上下文,其中一个根上下文在多个DispatcherServlet(或其他Servlet)实例中共享,每个实例也都有自己的上下文配置。

根上下文通常包含基础bean,例如需要跨多个Servlet实例共享的数据库和业务服务。这些bean是可以继承的,可以在子上下文中重写(即重新定义),子上下文通常包含当前Servlet的本地bean。

层次结构如下:

  • Servlet WebApplicationContext:这是对J2EE三层架构中的web层进行配置,如控制器(controller)、视图解析器(view resolvers)等相关的bean。通过spring mvc中提供的DispatchServlet来加载配置,通常情况下,配置文件的名称为spring-mvc.xml。
  • Root WebApplicationContext:这是对J2EE三层架构中的service层、dao层进行配置,如业务bean,数据源(DataSource)等。通常情况下,配置文件的名称为applicationContext.xml。在web应用中,其一般通过ContextLoaderListener来加载。

这两个上下文,都是在容器启动的过程中初始化的,那么容器是怎么知道他们的呢,我们知道web容器的初始化首先是加载web.xml文件,并且对其中定义的listener和servlet等进行相应的加载和初始化。那么我们可以自定义一个listener(ContextLoaderListener)或servlet(DispatcherServlet),容器会根据web.xml加载和初始化他们,而他们则负责加载相应的配置文件。

在web.xml配置文件中,有两个主要的配置:ContextLoaderListener和DispatcherServlet。同样的关于spring配置文件的相关配置也有两部分:context-param和DispatcherServlet中的init-param。那么,这两部分的分别表示是Spring 容器的初始化和web容器的初始化。DispatcherServlet和ContextLoaderListener提供了在Web容器中对spring的接口。ServletContext为Spring的IOC容器提供了一个宿主环境,在宿主环境中,Spring MVC建立起了一个IOC容器体系。

看下web.xml的配置:

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!—创建Root WebApplicationContext-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <!—创建Servlet WebApplicationContext-->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

2、根上下文初始化

 Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext接口扩展ApplicationContext,使得拥有web功能,WebApplicationContext接口默认的实现是XmlWebApplicationContext。那么,Spring MVC是如何在web环境中创建IoC容器呢?先看一下WebApplicationContext的源码:

public interface WebApplicationContext extends ApplicationContext {

	//用于在ServletContext中存取根上下文
	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

	String SCOPE_REQUEST = "request";

	String SCOPE_SESSION = "session";

	String SCOPE_GLOBAL_SESSION = "globalSession";

	String SCOPE_APPLICATION = "application";

	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

	//取得当前web容器的ServletContext
	ServletContext getServletContext();

}

在Spring MVC中,spring的上下文是以父子的继承结构存在的。Web环境(ServletContext)中存在一个根上下文,这个上下文是整个应用的根上下文,是其他上下文的双亲上下文。同时Spring MVC也对应的持有一个独立的上下文,它是根上下文的子上下文。

由ContextLoaderListener首先启动的上下文为根上下文,该上下文是与ServletContext相伴而生的,在根上下文的基础上,Spring MVC对应持有的一个用来管理Controller的子上下文。下图是ContextLoaderListener继承关系,它实现了ServletContextListener接口,这个接口提供了与Servlet生命周期结合的回调,比如contextInitialized和contextDestroyed方法。建立WebApplicationContext的过程是在contextInitialized的接口实现中完成的,具体的载入IOC容器的过程是由ContextLoader来完成的。

ContextLoaderListener继承结构:

ContextLoaderListener源码:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener() {
	}

	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}


	/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}


	/**
	 * Close the root web application context.
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}

}

从ContextLoaderListener源码可以看出,实现的是ServletContextListener接口,这个接口里的方法会被Web容器的生命周期调用。因为ServletContextListener是ServletContext的监听者,当servletContext启动或者停止的时候,会触发相应的事件,监听器ServletContextListener会接收到事件,会做出相应的响应,比如这里的contextInitialized方法和contextDestroyed方法。Spring IOC容器(根上下文)的生成与销毁就是通过这个两个方法触发的,所以根上下文是与ServletContext相伴而生的。

所以当Web应用启动时,contextInitialized方法会执行载入根上下文(IOC容器),具体过程是首先从Servlet事件中得到ServletContext,然后以ServletContext为宿主环境,载入根上下文(IOC容器),具体的载入过程是由ContextLoader处理的。

ContextLoader加载根上下文的源码:

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//整个web应用,只能有一个根上下文
		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 {
			//将上下文存储在本实例变量中,以确保它在ServletContext关闭时可用。
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						//设置根上下文的父上下文
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//将根上下文放置在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;
		}
	}
	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		//根据web.xml中的配置,决定用那种WebApplicationContext,默认用XmlWebApplicationContext
		Class<?> contextClass = determineContextClass(sc);
		//判断contextClass是否继承ConfigurableWebApplicationContext或者是其接口实现
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		//实例化根上下文
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
	//设置根上下文的各个参数,然后通过refresh启动容器的初始化
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		//设置根上下文id
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		//设置ServletContext
		wac.setServletContext(sc);
		//设置spring配置文件路径(web.xml的context-param节点配置)
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		//初始化ServletContext的属性(web.xml中的context-param、init-param、系统环境变量等)
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		//在refresh之前,允许我们对ConfigurableWebApplicationContext做一些定制处理
		//可以参考ApplicationContextInitializer的作用
		customizeContext(sc, wac);
		//启动容器的初始化,例如:bean的定义解析,都是在这一步完成的
		wac.refresh();
	}

从上面的源码中可以看出,IOC容器在web容器中的启动过程,与在应用中启动IOC容器的方式相似,不同的是这里需要考虑web容器的环境的特点,比如各种参数的设置,IOC容器与web容器ServletContext的结合等。在初始化上下文以后,该上下文被存储再ServletContext中,这样就建立了一个全局的关于整个应用的上下文,即所谓的根上下文。同时在启动Spring MVC的时候,DispatchServlet在进行自己持有的上下文的初始化时,是将此根上下文设置为自己的双亲上下文的。

3、Spring MVC容器(子上下文)的初始化

以上是web容器中根上下文的加载与初始化,在完成对ContextLoaderListener的初始化以后,web容器开始初始化DispatchServlet,DispatchServlet会建立自己的上下文来管理Spring MVC的bean对象。在建立这个自己持有的上下文的时候,会从ServletContext中得到根上下文作为DispatchServlet持有的上下文的双亲上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文也保存到ServletContext中。

DispatchServlet通过继承FrameworkServlet和HttpServletBean而继承了HttpServlet。HttpServletBean是Spring对于Servlet最低层次的抽象。在这一层抽象中,Spring会将这个Servlet视作是一个Spring的bean,并将web入口配置文件web.xml中DispatchServlet定义的init-param参数中的值作为bean的属性注入进来。

DispatchServlet的继承关系:

DispatcherServlet也是一个Servlet,根据Servlet规范的定义,Servlet中的两大核心方法init方法和service方法:

1、init方法

在整个系统启动时运行,且只运行一次。因此,在init方法中我们往往会对整个应用程序进行初始化操作。这些初始化操作可能包括对容器(WebApplicationContext)的初始化、组件和外部资源的初始化等等。 

2、service方法

在整个系统运行的过程中处于侦听模式,侦听并处理所有的Web请求。因此,在service及其相关方法中,我们看到的则是对Http请求的处理流程。具体可参考另一篇文章:https://blog.csdn.net/matt8/article/details/105166352

init()方法在HttpServletBean中,源码如下:

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

		//获取web.xml中DispatchServlet中定义的<init-param>信息
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				//生成一个BeanWrapper,将当前的这个Servlet类转化为一个BeanWrapper,从而能够以Spring的方式来对init-param的值进行注入
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				//将init-param中的值注入
				bw.setPropertyValues(pvs, true);
			}
			catch (BeansException ex) {
				if (logger.isErrorEnabled()) {
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				}
				throw ex;
			}
		}

		//调用子类的initServletBean进行具体的初始化
		initServletBean();
		
		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

initServletBean()方法是在FrameworkServlet中实现的,源码如下:

	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 {
			//对Spring mvc的容器(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");
		}
	}

	protected WebApplicationContext initWebApplicationContext() {
		//这里调用WebApplicationContextUtils静态类来从ServletContext中得到根上下文,使用这个根上下文作为当前MVC上下文的双亲上下文。
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			//如果一个context的实例被注入了,直接用
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					//如果此上下文还没有初始化,就设置上下文的参数,如双亲上下文、context id等
					if (cwac.getParent() == null) {
						//如果被注入的context实例没有双亲上下文,则将根上下文设置为其双亲上下文
						cwac.setParent(rootContext);
					}
					//设置其他参数,并启动容器初始化
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			//没有注入的context实例,这里从ServletContext中查找是否有context实例已经注册到了servlet context了
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			//这个servlet没有context实例,则创建一个上下文,以根上下文为双亲下文
			wac = createWebApplicationContext(rootContext);
		}

		//刷新上下文(执行组件的初始化),这个方法由子类DispatchServlet的方法实现
		if (!this.refreshEventReceived) {
			onRefresh(wac);
		}

		//将这个上下文,作为这个servlet的一个属性
		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;
	}

	protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
		//根据web.xml中的配置,决定用那种WebApplicationContext,默认用XmlWebApplicationContext
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

		//设置上下文的相关信息,并启动初始化
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);
		wac.setConfigLocation(getContextConfigLocation());

		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}

Spring MVC的初始化是在DispatchServlet的initStrategies()方法中完成的,包括对各种MVC框架的实现元素,比如支持国际化的LocaleResolver、支持request映射的HandlerMappings、以及视图生成的ViewResolver等的初始化。这里以HandlerMappings为例来说明MVC框架元素的初始化过程。

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

	//初始化默认的Spring Web MVC框架使用的策略(如HandlerMapping)
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			//这里导入所有的HandlerMapping Bean,这些Bean可以是在当前的DispatchServlet的IOC容器,
			//也可以是其双亲上下文中的,这里detectAllHandlerMappings默认是为true的,从所有的IOC容器中取
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			//从当前IOC容器中通过getBean获取handlerMapping
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		//如果没有找到handlerMappings,设置默认的handlerMapping,默认值设置在DispatcherServlet.properties中
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

4、Spring MVC 上下文初始化流程图

参考:

https://blog.csdn.net/and1kaney/article/details/51214149

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值