springMVC源码简读——1.1 Root WebApplicationContext 容器

容器概述

最开始我们没有使用springboot的时候,还在使用web.xml进行配置的时候,就是在web中配置spring的参数来实现servlet和springMVC的集成。

一份古老的web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<!--指定Spring Bean的配置文件所在目录-->
  <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath:applicationContext.xml
		</param-value>
	</context-param>
	
	<!--配置了一个ContextLoaderListener-->
	<listener>
		<listener-class>
			org.springframework.web.context.ContextLoaderListener
		</listener-class>
	</listener>

		<!--配置解码-->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

	<!--配置自定义的Filter-->
	<filter>
		<filter-name>checkUserfilter</filter-name>
		<filter-class>com.xxxx.common.util.CheckUserFilter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>checkUserfilter</filter-name>
		<url-pattern>*.do</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>checkUserfilter</filter-name>
		<url-pattern>*.jsp</url-pattern>
	</filter-mapping>
	
	<!--Spring MVC配置-->
	<servlet>
		<servlet-name>spring-dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:applicationContext-servlet.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring-dispatcher</servlet-name>
		<url-pattern>*.do</url-pattern>
	</servlet-mapping>

</web-app>

调试方法

其调试代码主要在:

org/springframework/web/context/ContextLoaderTests.java 类中,其主要入口方法在:testContextLoaderListenerWithDefaultContext

类图

加载容器的关键,在于配置的org.springframework.web.context.ContextLoaderListener其本身是实现了javax.servlet.ServletContextListener接口,通过其内部方法初始化容器

其类关系为:

在这里插入图片描述

其中两个接口都属于java的Servlet范畴

启动加载

从web.xml的配置中我们可以看到,WebApplicationContext 的加载主要是通过监听器(ContextLoaderListener )来进行的。服务器启动的时候会被监听器坚挺到,从而初始化Root WebApplicationContext 容器。那么先看看监听器的相关逻辑。

ContextLoaderListener

本身实现ServletContextListener 接口,主要负责Servlet的关闭和启动,进行WebApplicationContext 的初始化和销毁
监听器的地址: org/springframework/web/context/ContextLoaderListener.java

构造方法

    public ContextLoaderListener() {
	}

	/**
	 * 可以直接传递一个 WebApplicationContext 对象,那样,
	 * 实际 ContextLoaderListener 就无需在创建一个新的 WebApplicationContext 对象
	 * 再其父类ContextLoader中 context直接被设置成了传入的参数
	 */
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

其构造方法根据参数可以得知一个市负责创建WebApplicationContext ,另外一个是接收其他途径传递过来的WebApplicationContext 而不自己进行创建。

contextInitialized

// ContextLoaderListener.java

    /**
	 * Initialize the root web application context.
	 * 初始化 WebApplicationContext 对象
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
	
// ContextLoader.java  具体初始化的方法在其父类ContextLoader中,下面有介绍
	

根据servlet的事件去进行WebApplicationContext的初始化

contextDestroyed

// ContextLoaderListener.java

	/**
	 * Close the root web application context.
	 * 销毁 WebApplicationContext 容器的逻辑
	 */
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
	
// ContextLoader.java  具体销毁的方法在其父类ContextLoader中,下面有介绍

根据servlet的事件去进行WebApplicationContext的销毁,ContextCleanupListener.cleanupAttributes方法具体逻辑是从ServletContext获得属性名称,然后依次根据属性名称获取属性进行销毁。逻辑很简单,代码就不贴了,有兴趣可以自己看看

ContextLoader

ContextLoader是具体进行业务实现的相关类。 ContextLoader的地址: org/springframework/web/context/ContextLoader.java

构造方法

主要被分为两部分

  • defaultStrategies 静态属性
  • context 属性

defaultStrategies相关

这里是ContextLoader一些静态配置


	public static final String CONTEXT_ID_PARAM = "contextId";
	public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";

	public static final String CONTEXT_CLASS_PARAM = "contextClass";

	public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";

	public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";

	private static final String INIT_PARAM_DELIMITERS = ",; \t\n";

	private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";

	// 默认的配置 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 {
			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());
		}
	}

context相关

/**
	 * WebApplicationContext 对象
	 * The 'current' WebApplicationContext, if the ContextLoader class is
	 * deployed in the web app ClassLoader itself.
	 */
	@Nullable
	private static volatile WebApplicationContext currentContext;


	/**
	 * Root WebApplicationContext 对象
	 * The root WebApplicationContext instance that this loader manages.
	 */
	@Nullable
	private WebApplicationContext context;

目前ContextLoader中存在的容器参数

initWebApplicationContext

进行WebApplicationContext 对象的初始化

	
	/**
	 * 初始化 WebApplicationContext 对象
	 */
	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//  若已经存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 对应的 WebApplicationContext 对象,则抛出 IllegalStateException 异常。
		// 例如,在 web.xml 中存在多个 ContextLoader 。
		// 如果 web.xml 如果定义了多个 ContextLoader,会报错
		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!");
		}

		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		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) {
				// 初始化 context ,即创建 context 对象
				this.context = createWebApplicationContext(servletContext);
			}
			// 如果是 ConfigurableWebApplicationContext 的子类,如果未刷新,则进行配置和刷新
			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);
					}
					// 配置 context 对象,并进行刷新
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			// 记录在 servletContext 中
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

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

			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}
			// 返回 context
			return this.context;
		}
		catch (RuntimeException | Error ex) {
			// 当发生异常,记录异常到 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 中,不再重新初始化。
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

createWebApplicationContext

开始创建WebApplicationContext对象,在初始化容器的时候需要使用servletContext来构建WebApplicationContext的方法就是createWebApplicationContext

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		// 获得 context 的类
		Class<?> contextClass = determineContextClass(sc);
		// 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
		// 创建 context 的类的对象
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
	
	
	
		/**
	 * 获得 context 的类
	 */
	protected Class<?> determineContextClass(ServletContext servletContext) {
		// 获得参数 contextClass 的值
		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);
			}
		}
		// 从 defaultStrategies 获得该类
		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);
			}
		}
	}
	

configureAndRefreshWebApplicationContext

进行WebApplicationContext配置并刷新环境

// ContextLoader.java
	// 配置 ConfigurableWebApplicationContext 对象,并进行刷新
	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		// 如果 wac 使用了默认编号,则重新设置 id 属性
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// The application context id is still set to its original default value
			// -> assign a more useful id based on available information
			// 情况一,使用 contextId 属性
			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()));
			}
		}
		// 设置 context 的 ServletContext 属性
		wac.setServletContext(sc);
		// 设置 context 的配置文件地址
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

		// The wac environment's #initPropertySources will be called in any case when the context
		// is refreshed; do it eagerly here to ensure servlet property sources are in place for
		// use in any post-processing or initialization that occurs below prior to #refresh
		// 获得wac的环境,如果为配置,则初始化数据
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		// 执行自定义初始化 context
		customizeContext(sc, wac);
		// 刷新context
		wac.refresh();
	}

closeWebApplicationContext

进行容器关闭的操作

	
		/**
	 * Close Spring's web application context for the given servlet context.
	 * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have
	 * to override this method as well.
	 * @param servletContext the ServletContext that the WebApplicationContext runs in
	 *                       关闭 WebApplicationContext 容器对象
	 */
	public void closeWebApplicationContext(ServletContext servletContext) {
		servletContext.log("Closing Spring root WebApplicationContext");
		try {
			// 关闭 context
			if (this.context instanceof ConfigurableWebApplicationContext) {
				((ConfigurableWebApplicationContext) this.context).close();
			}
		}
		finally {
			// 移除 currentContext 或 currentContextPerThread
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = null;
			}
			else if (ccl != null) {
				currentContextPerThread.remove(ccl);
			}
			// 从 ServletContext 中移除
			servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
		}
	}

总结

其实WebApplicationContext 的加载内容还算清晰。
从web读取配置加载ContextLoaderListener并按部就班的初始化ServletContext内容,做好容器生命周期的监控。在springboot为主流的今天,配置web.xml的项目已经很少了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值