希望大家耐心看完,有问题请给我留言,小到错别字,大到讲解错误,我将竭尽所能进行改善和解答。
写在前面
在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
结尾的,我想这也正是名称根容器
的来源吧!