Web容器整合ApplicationContext源码

一、web.xml配置开始

在使用Tomcat等容器整合Spring的时候,需要在web.xml中添加如下的配置:

  <!-- spring的环境监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

配置中首先引入了一个监听器ContextLoaderListener,该监听器主要功能就是创建ApplicationContext并且将其与ServletContext相互绑定。其次,为了让ApplicationContext获取到我们的配置,还需要在context-param标签中设置我们的配置文件位置contextConfigLocation。

二、ContextLoaderListener监听器实现

ContextLoaderListener继承自ContextLoader和ServletContextListener接口。该父类完成创建、初始化ApplicationContext的功能,而ServletContextListener 接口使得在ServletContext初始化完成后,Spring有机会可以创建ApplicationContext并且将其绑定到ServletContext上。

下面是ContextLoaderListener的实现。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener() {
	}

	//使用给定的WebApplicationContext创建监听器实例
	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());
	}

}

ServletContext初始化后会调用contextInitialized方法,而方法中就是创建、装配ApplicationContext的地方(Web环境下,ApplicationContext的类型是WebApplicationContext):

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		//WebApplicationContext已绑定到ServletContext
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(...);
		}

		Log logger = LogFactory.getLog(ContextLoader.class);
		//日志打印,略
		long startTime = System.currentTimeMillis();

		try {
			//创建WebApplicationContext
			if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					//为当前上下文配置父ApplicationContext(不常用)
					//如果initContextParam中配置父ApplicationContext相关属性的话(locatorFactorySelector、parentContextKey)
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//配置并刷新WebApplication
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			//将refesh后的WebApplicationContext绑定到servletContext
			//WebApplicationContext在上一步中也会将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);
			}
			//日志打印,略

			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;
		}
	}

代码流程:

  1. 创建WebApplicationContext实例
  2. 配置WebApplicationContext属性,并调用其refresh方法,刷新上下文(这一步中也会将ServletContext设置到WebApplicationContext属性中)
  3. 将初始化好的WebApplicationContext绑定到ServletContext中

1、创建WebApplicationContext

创建的代码在createWebApplicationContext实现:

	protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		//确定WebApplicationContext的class使用哪个
		Class<?> contextClass = determineContextClass(sc);
		//确保使用的class继承自ConfigurableWebApplicationContext
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("...");
		}
		//通过放射实例化
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

代码流程分为两步:

  1. 确定所使用的ApplicationContext的类
  2. 通过反射,实例化ApplicationContext

确定类型:

	protected Class<?> determineContextClass(ServletContext servletContext) {
		//获取web.xml中contextInitParam的contextClass属性
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		//如果用户配置了contextClass属性,就加载对应class并返回结果
		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 {
			//使用默认的WebApplicationContext的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);
			}
		}
	}

defaultStrategies定义声明如下:

	private static final Properties defaultStrategies;

	static {
		try {
			//加载与ContextLoader.class在同一目录下的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());
		}
	}

Spring回家再与ContextLoader.class在同一目录下的ContextLoader.properties文件,文件声明如下:

org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext

因此,WebApplicationContext的默认实现类是XmlWebApplicationContext

实例化

	public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
		Assert.notNull(clazz, "Class must not be null");
		//如果是接口,抛出异常
		if (clazz.isInterface()) {
			throw new BeanInstantiationException(clazz, "Specified class is an interface");
		}
		try {
			//获取默认的无参构造器,通过反射实例化
			return instantiateClass(clazz.getDeclaredConstructor());
		}
		catch (NoSuchMethodException ex) {
			throw new BeanInstantiationException(clazz, "No default constructor found", ex);
		}
	}

下面是反射创建对象的代码:

	public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
		Assert.notNull(ctor, "Constructor must not be null");
		try {
			ReflectionUtils.makeAccessible(ctor);
			return ctor.newInstance(args);
		}
		catch ...
	}

2、配置并刷新

	protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		//为ApplicationContext设置一个runId
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			//提取配置文件中contextId属性
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				//未配置,使用默认的.
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		//将ServletContext实例设置到WebApplicationContext中
		wac.setServletContext(sc);
		//获取web.xml中配置的contextConfigLocation属性
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			//设置到WebApplication中
			wac.setConfigLocation(configLocationParam);
		}

		// 将ServletContext封装后设置到WebApplicationContext中,方便获取initParam
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		//获取配置的ApplicationContextInitializer并对ApplicationContext进行初始化
		customizeContext(sc, wac);
		//刷新上下文
		wac.refresh();
	}

代码流程:

  1. 设置runId
  2. 将ServletContext绑定到WebApplicationContext中
  3. 设置contextConfigLocation属性
  4. 将ServletContext封装后设置到WebApplicationContext中,方便获取initParam
  5. 获取配置的ApplicationContextInitializer,并对ApplicationContext设置初始化属性(通过globalInitializerClasses、contextInitializerClasses属性配置)
  6. 刷新上下文

再刷新上下文后,ApplicationContext就已经创建好了,可以在Web环境中使用

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值