一、web环境中创建spring根应用上下文
在SSM整合时,会在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">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!--
servlet默认是懒加载,也就是当servlet第一次被访问时,才会加载被访问的servlet,
如果配置了<load-on-startup>,就是在tomcat容器启动时就加载当前servlet
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/ 匹配除jsp以外的所有的请求路径
/* 匹配所有请求路径
*.do 匹配所有以.do结尾的所有请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置的监听器就是ContextLoaderListener:
org.springframework.web.context.ContextLoaderListener
这个监听器的继承关系如下:
在Servlet容器启动时,会调用ServletContextListener的contextInitialized(ServletContextEvent even)方法,在此方法中会调用从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;
}
}
在上述代码中,我们可以看到,通过下面的语句创建了一个WebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
通过下面的语句,把创建的WebApplicationContext放到ServletContext域中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
二、web环境中创建SpringMVC的应用上下文
在web.xm中,可以看到在Servlet启动时,会创建DispatcherServlet。在Servlet初始化时,会调用Servlet的初始化方法init()方法。
下图时DispatcherServlet的继承关系图:
在HttpServletBean中的init()方法中,会读取配置在ServletConfig中的初始化参数,以及调用initServletBean()这个方法,而HttpServletBean中的方法是空方法,最后会调用FrameworkServlet类中的initServletBean()方法。代码HttpServletBean中init方法的代码 如下:
public final void init() throws ServletException {
// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
initServletBean();
}
HttpServletBean的initServletBean是空方法,因此会调用子类FrameworkServlet的initServletBean(),FrameworkServlet.initServletBean()代码如下:
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
initServletBean方法调用initWebApplication()方法创建SpringMVC的应用上下文,然后把创建的应用上下文赋值给FrameworkServlet的webApplicationContext属性。至此springMVC的应用上下文就创建完成。
private WebApplicationContext webApplicationContext;
三、spring的根应用上下文与springMVC上下文的父子关系
在第一部分描述了ContextLoaderLister是如何创建Spring应用的根应用上下文,第二部分描述了DispatcherServlet是如何创建SpringMVC应用上下文,这两个上下文是什么关系呢?
其实在SSM整合时,这两个上下文是父子关系,这里的父子关系并不是继承关系,而是组合关系,与JVM中父类加载器的概念一样。在SpringMVC的应用上下文中有一个parent属性,这个属性指向Spring根应用上下文。
如下图:
那么这个SpringMVC创建过程是怎么样的呢?
在第二部分中指出,SpringMVC的应用上下文是FrameworkServlet的initWebApplicationContext()创建的,进入initWebApplicationContext()方法
protected WebApplicationContext initWebApplicationContext() {
//从ServletContext中获取根应用上下文
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// 一个SpringMVC容器实例已经存在,则使用这个容器
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
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 -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
/**
在构造时如果没有SpringMVC的应用上下文被创建,则查找ServletContext,看看是否在ServletContext存在SpringMVC应用上下文
*/
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
/**
如果在这个FrameworkServlet中没有找到任何SpringMVC的应用上下文,则创建一个。
在createWebApplicationContext(rootContext)方法中创建一个SpringMVC应用上下文,并把根应用上下文rootContext赋值给
被创建的SpringMVC应用上下文的parent属性。
*/
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
注意:在initWebApplicationContext方法的最后,创建的SpringMVC的应用上下文也是要发布到ServletContext的属性中,其中key值是org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet ,value就是创建的SpringMVC的应用上下文。代码如下:
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
FrameworkServlet的createWebApplication(Application parent)代码如下:
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
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());
// 把根应用上下文赋值给SpringMVC的应用上下文
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}