springmvc 的ROOT WebApplicationContext

希望大家耐心看完,有问题请给我留言,小到错别字,大到讲解错误,我将竭尽所能进行改善和解答。

写在前面

在spring MVC中spring的容器目前据我所知有2个,如果简单的从三层架构来说的的话一个是Controller相关的容器,另一个是service+dao相关的容器,本次讨论的ROOT WebApplicationContext其实就是后者service+dao相关的容器。

1:初始化入口在哪里?

在servlet规范中定义了很多的监听器,不同的监听器可以对外暴露不同事件的监听,主要分为如下2类:

1.1:核心对象生命状态变化相关

javax.servlet.ServletContextListener是用来定义监听javax.servlet.ServletContext对象的创建和销毁到,其实spring MVC ROOT WebApplicationContext对象创建也正是利用此监听器,再比如javax.servlet.ServletReqeustListener是用来监听请求的创建和销毁的,javax.servlet.HttpSessionListener是用来定义监听session的创建和销毁的。

1.2:对象属性变化相关

如context属性变化的监听器javax.servlet.ServletContextAttributeListener,request属性变化监听器javax.servlet.ServletRequestAttributeListener

1.3:入口

springMVC利用了javax.servlet.ServletContextListener来在web容器启动完毕后进行相关代码的初始化,定义的子类为org.springframework.web.context.ContextLoaderListener,首先看下javax.servlet.ServletContextListener接口定义:

public interface ServletContextListener extends EventListener {
    public default void contextInitialized(ServletContextEvent sce) {
    }
    public default void contextDestroyed(ServletContextEvent sce) {
    }
}

其中的contextInitialized方法会在web容器启动时调用,org.springframework.web.context.ContextLoaderListener正是实现了该接口,源码如下:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
   @Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
}

其与servlet的关系如下图:
在这里插入图片描述

其中ROOT WebApplicationContext的初始化正是在initWebApplicationContext方法中执行完成了,也就是入口程序了。当然还需要在web.xml中进行配置,如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
</web-app>

2:正式开始

2.1:web容器调用contextInitialized

源码:

org.springframework.web.context.ContextLoaderListener#contextInitialized
@Override
publicvoid contextInitialized(ServletContextEvent event) {
	initWebApplicationContext(event.getServletContext());
}

这里调用的方法initWebApplicationContext是在父类org.springframework.web.context.ContextLoader类中定义的。下面就来看initWebApplicationContext方法。

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!");
		}

		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) {
				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);
				}
			}
			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.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

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

下面开始分析重要代码。

2.2:判断根容器是否已经创建

代码:

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!");
}

简单说下ROOT WebapplicationContext的存储,以tomcat为例其在创建成功后是存储在org.apache.catalina.core.ApplicationContext中Map中的,key就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,这样后续的使用发起点就都是ServletContext了,对于tomcat其实就是org.apache.catalina.core.ApplicationContextFacade类,用于存储的map源码如下:

org.apache.catalina.core.ApplicationContext#attributes
protected Map<String,Object> attributes = new ConcurrentHashMap<>();

其中记录到ServletContext源码如下:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

这样也就是为什么判断是否创建成功通过这种方式了。

2.3:创建根对象

  • 代码
if (this.context == null) {
	this.context = createWebApplicationContext(servletContext);
}

具体代码:

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
	// 获取要创建的ApplicationContext的对象类型
	Class<?> contextClass = determineContextClass(sc);
	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);
}
2.3.1: determineContextClass
  • 源码
protected Class<?> determineContextClass(ServletContext servletContext) {
	// <1>
	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 {
		// <2>
	  contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
		try {
			// <3>
			return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
		}
		catch (ClassNotFoundException ex) {
			throw new ApplicationContextException(
					"Failed to load default context class [" + contextClassName + "]", ex);
		}
	}
}

<1>处代码String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);是判断是否在web.xml中通过<context-prams>配置了名称为contextClass的ServletContext参数,如果配置了则直接使用,一般我们不会去配置,否则是否默认的,默认的是通过和org.springframework.web.cotext.ContextLoader同位置的ContextLoader.properties配置文件配置的:如下:

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

读取的方式是通过org.springframework.core.io.ClassPathResource类和org.springframework.core.io.support.PropertiesLoaderUtils工具类直接读取到java.util.Properties集合类中,源码如下:

private static final Properties defaultStrategies;
static {
	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());
	}
}

<2>处就是获取在Context.properties文件中默认配置的WebApplicationContext,<3>ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());就是完成Class对象的创建,并返回。

2.3.2: 创建对象实例

直接利用org.springframework.bean.BeanUtils创建对象实例,源码如下:

return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

到这里根对象就已经创建出来了。

2.4: 配置根对象并刷新

  • 代码
configureAndRefreshWebApplicationContext(cwac, servletContext);
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    // <1>
	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()));
		}
	}
	// <2>
	wac.setServletContext(sc);
	// <3>
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}
	// <4>
	wac.refresh();
	}

<1>处代码判断如果是默认的id则先使用contextId配置参数,如果没有配置的话则生成,如果配置的话通过在web.xml进行如下配置:

<context-param>
	<param-name>contextId</param-name>
	<param-value>dongsir-root-web-application-context-id</param-value>
</context-param>

<2>处代码为根对象设置ServletContext。<3>处获取spring bean的配置文件,默认为classpath下的applicationContext.xml这里获取的是在·web.xml·配置的用户自定义的,可能配置如下:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:WEB-INF/applicationContext.xml</param-value>
</context-param>

<4>完成刷新初始化spring容器。

2.5: 设置跟容器到ServletContext中

  • 代码:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

这里的key是.ROOT结尾的,我想这也正是名称根容器的来源吧!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值