文章目录
- 前言
- SpringMVC 和 web.xml
- ServletContextListener
- SpringMVC 对 ServletContextListener 的实现 ContextLoaderListener
- ContextLoaderListener 的 UML
- org.springframework.web.context.ContextLoaderListener 处理逻辑 UML
- ContextLoader 决定 WebApplicationContext 的具体实现类
- ContextLoaderListener 默认加载配置文件路径:/WEB-INF/applicationContext.xml
- 自定义 配置文件路径:web.xml增加一个全局参数配置 context-param:contextConfigLocation
- ContextLoaderListener 的配置可以省略,但不建议这么做
- 源码跟踪
- 入口 ContextLoaderListener.contextInitialized(
- ContextLoader.initWebApplicationContext(
- ContextLoader.createWebApplicationContext(
- ContextLoader.configureAndRefreshWebApplicationContext(
- StandardServletEnvironment.initPropertySources(
- ContextLoader.customizeContext(
- AbstractApplicationContext.refresh(
前言
体能状态先于精神状态,习惯先于决心,聚焦先于喜好。
SpringMVC 和 web.xml
SpringMVC 的入口在 web.xml
在该文件中我们可以进行多项配置,比如全局变量、过滤器、 < listener> 和 Spring 的 DispatcherServlet 等等。
ServletContextListener
该接口的完整路径为 javax.servlet.ServletContextListener
从源码注释中我们可以知道:
1、一个类实现一个ServletContextListener 借口,这种实现可以有多个;
2、该接口有两个方法 contextInitialized 和 contextDestroyed,二者都可以直接访问 ServletContext,ServletContext 是整个web 项目启动阶段的核心所在
3、contextInitialized 会在 ServletContext 初始化阶段运行,且早于 filters 或 servlets 的配置运行,期间我们完全可以做一些前期准备工作,比如配置文件加载、健康检查、数据库密码解密等等
4、contextDestroyed 会在 ServletContext 停止阶段运行,且晚于 filters 或 servlets 的配置运行,其运行对一些资源进行销毁。
public interface ServletContextListener extends EventListener {
/**
* 在 web 应用初始化阶段会调用本方法;
* 可以使用多个类实现该接口,即你可以在一个应用中拥有多个 ServletContextListener;
* ServletContextListener的contextInitialized方法会在 web.xml 中所有的 filters 或 servlets 配置前运行
* 参数 sce the ServletContextEvent 包含正在初始化的 ServletContext,即你可以在该方法的实现中直接操作 ServletContext
*/
public void contextInitialized(ServletContextEvent sce);
/**
* 当 ServletContext 将要停止时被调用
*
* 在web.xml所有的 servlets 和 filters 配置执行完毕并销毁后 contextDestroyed 方法才会被执行
* 参数 sce 可以访问正在被销毁的 ServletContext 对象,即你可以在方法实现中操作 ServletContext 对象
*/
public void contextDestroyed(ServletContextEvent sce);
}
自己实现一个 ServletContextListener
package com.bestcxx.cn.webrecord.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 自定义一个 ServletContextListener
* @Author jie.wu
*/
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println(this.getClass().getName()+"输出了");
System.out.println(this.getClass().getName()+"输出了");
System.out.println(this.getClass().getName()+"输出了");
System.out.println(this.getClass().getName()+"输出了");
System.out.println(this.getClass().getName()+"输出了");
//定义一般参数
sce.getServletContext().setAttribute("name","jecket");
//定义初始化类型参数,比如数据库连接的用户名密码解密等
sce.getServletContext().setInitParameter("demo","demo");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
//todo
}
}
在 web.xml 中使用 < listener> 配置 ServletContextListener
实现了 ServletContextListener 的类后还需增加配置才能生效
通过 web.xml < listener> 标签指定相关类,可以有多个< listener>标签并列存在:
<listener>
<listener-class>com.bestcxx.cn.webrecord.listener.MyServletContextListener</listener-class>
</listener>
其中 < listener-class> 标签包围着一个类,该类实现了 ServletContextListener 接口。
这个类你也可以自己写,并且,一个web.xml 文件可以包含多个 < listener> 代码组合
SpringMVC 对 ServletContextListener 的实现 ContextLoaderListener
SpringlMVC 对 ServletContextListener 的实现是 ContextLoaderListener,其包含了更多的SpringMVC 自身的特性。
其最主要的功能就是加载 SpringMVC 相关的配置文件,进行初始化工作,核心逻辑是初始化 WebApplicationContext 实例并存放至 ServletContext 中。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener 的 UML
org.springframework.web.context.ContextLoaderListener 处理逻辑 UML
ContextLoader 决定 WebApplicationContext 的具体实现类
如果你不指定,就会采用 Spring 自带的配置文件的类 XmlWebApplicationContext 进行实例化,除非你自己实现了个性的实例化类。
实例化后的 WebApplicationContext 会被放入到上下文中供应用使用。扫描二维码关注公众号,回复: 7196413 查看本文章
仅允许加载一次 WebApplicationContext 对象
当加载完毕后被实例化的 WebApplicationContext 对象会被保存到 上下文中
//值为 org.springframework.web.context.WebApplicationContext.ROOT
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE=
WebApplicationContext.class.getName() + ".ROOT"
//···中间代码略
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
加载仅允许加载一次,即你只能在 web.xml 中配置一次 ContextLoaderListener
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!");
}
默认加载配置文件 ContextLoader.properties
注意UML中的 ContextLoader,该类的静态方法加载了一个默认的配置文件 ContextLoader.properties
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());
}
}
ContextLoader.properties 文件内容只有一行,即默认情况下 WebApplicationContext 的实现类为 XmlWebApplicationContext
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
你可以自定义 WebApplicationContext 的实现类
对于 WebApplicationContext 的具体实现类,你可以通过自定义的ServletContextListener 或者全局变量提前将变量放入上下文中,名称为 contextClass,
它就会使用你提供的类为 WebApplicationContext 提供一个实例化对象。
当然,你没必要自己写,如果仅仅作为实验的话——看看报错信息也可以,如果不想看报错信息就直接将value 写成 org.springframework.web.context.support.XmlWebApplicationContext
- 自定义 ServletContextListener
注意是 setInitParameter
@Override
public void contextInitialized(ServletContextEvent sce) {
//定义一般参数
sce.getServletContext().setAttribute("name","jecket");
//定义初始化类型参数-指定WebApplicationContext 的具体实例化类
//org.springframework.web.context.support.XmlWebApplicationContext 是默认的类,在 ContextLoader.properties 中有指定
sce.getServletContext().setInitParameter("contextClass","com.bestcxx.cn.webrecord.listener.MyWebApplicationContext");
}
- 通过全局变量 在web.xml 中
<context-param>
<param-name>contextClass</param-name>
<param-value>com.bestcxx.cn.webrecord.listener.MyWebApplicationContext</param-value>
</context-param>
- Spring 源码通过反射机制为 WebApplicationContext 生成实例化对象
如果你指定了具体实例化类,就用你指定的,否则就用默认的
public static final String CONTEXT_CLASS_PARAM = "contextClass";
//···中间代码略
protected Class<?> determineContextClass(ServletContext servletContext) {
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 {
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);
}
}
}
ContextLoaderListener 默认加载配置文件路径:/WEB-INF/applicationContext.xml
默认情况下,WebApplicationContext 由 XmlWebApplicationContext 实例化后,就会加载配置文件
,XmlWebApplicationContext 默认配置文件路径为 /WEB-INF/applicationContext.xml 文件,路径和名字都必须一模一样。
applicationContext.xml 内应该包含的内容是 Spring 容器基础能力相关的内容-比如数据库、业务、事务等功能bean,而不应该包含 controller 和页面的内容.
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {
/** Default config location for the root context */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";
自定义 配置文件路径:web.xml增加一个全局参数配置 context-param:contextConfigLocation
表达式支持1,2 挥着 * 通配符,即可以配置多个文件
<!--自定义SpringMVC 配置文件的位置
1、contextConfigLocation 名字不可变
2、可以使用通配符加载多个文件,如 classpath:*-applicationContext.xml-->
<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>
ContextLoaderListener 的配置可以省略,但不建议这么做
如果你的 web.xml 没有配置 ContextLoaderListener 的 < listener>,你会发现你的 SpringMVC 项目应用并不受什么影响。因为 SpringMVC 的 DispatcherServlet 也可以指定加载多个配置文件,但是不建议这么做,因为正常来说 ContextLoaderListener 负责加载基础功能的bean,会提前准备好,DispatcherServlet 加载 servlet相关的Bean,比如Controller 拦截器、视图处理器等.
关于 DispatcherServlet 的配置请移步:SpringMVC 的 DispatcherServet
源码跟踪
入口 ContextLoaderListener.contextInitialized(
web应用启动阶段会自动运行 web.xml 中配置的 < listener>
org.springframework.web.context.ContextLoaderListener.contextInitialized
注意 initWebApplicationContext(event.getServletContext());
/**
* 初始化web应用的根上下文
*/
@Override
public void contextInitialized(ServletContextEvent event) {
//初始化 webApplicationContext
initWebApplicationContext(event.getServletContext());
}
ContextLoader.initWebApplicationContext(
org.springframework.web.context.ContextLoader.initWebApplicationContext(
为给定的 servlet 上下文对象 初始化 Spring webAppictionContext对象,该webAppictionContext 可以在构造函数中提供,也可以创建一个新的对象,依据位于
CONTEXT_CLASS_PARAM 和 contextConfigLocation 这两个全局配置。
注意 createWebApplicationContext(servletContext); 这一句
注意 configureAndRefreshWebApplicationContext(cwac, servletContext);这一句
- 实例化 WebApplicationContext 对象,默认是 XmlWebApplicationContext
- XmlWebApplicationContext 间接实现了 ConfigurableWebApplicationContext ,如果该对象尚未 refreshed,则判断是否设置了 根web上下文对象,如果webApplicationContext 对象没有明确指定 父上下文对象,则确定一个根web上下文对象——只要符合标准即可
/**
* @param servletContext current servlet context
* @return the new WebApplicationContext
* @see #ContextLoader(WebApplicationContext)
* @see #CONTEXT_CLASS_PARAM
* @see #CONFIG_LOCATION_PARAM
*/
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//以 org.springframework.web.context.WebApplicationContext.ROOT 作为key保存 webApplicationContext 对象,该对象仅允许加载一次,所以这里先判断一下是否已经加载过
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
//将 上下文对象 保存到本地可以访问的实例中,以确保该上下文对象在服务停止时可以访问到
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// 如果 appliactionContext 对象还没有被 refreshed (refreshed 会提供父上下文对象,设置上下文id等配置),该值在 AbstractApplicationContext.refresh()的prepareRefresh()中修改为true
if (cwac.getParent() == null) {
//webApplicationContext 对象没有明确指定 父上下文对象,则确定一个根web上下文对象——只要符合标准即可
ApplicationContext parent = loadParentContext(servletContext);
//debug 发现,默认情况下这个值为null
cwac.setParent(parent);
}
//进行 webApplicationContext 的 配置和refresh 工作
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//初始化完毕后,放入到 servletContext 对象中,之后的Servlet请求就可以使用了
// key 为 org.springframework.web.context.WebApplicationContext.ROOT
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.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
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;
}
}
ContextLoader.createWebApplicationContext(
org.springframework.web.context.ContextLoader.createWebApplicationContext(
为这个加载器初始化 根 WebApplicationContext 对象,也可以是 默认的或者指定的上下文类 。
指定的类被期望为 ConfigurableWebApplicationContext 的子类,并且该方法不可被覆盖重写。
此外,在 refreshing the context 这个webApplicaionContext 对象之前,允许子类提前调用 customizeContext()方法,增加一些定制化的定义。
- 该方法内部将进行判断,如果你没有指定特殊的 webApplicationContext 的实例化类,则将使用Spring 默认的实例化类 org.springframework.web.context.support.XmlWebApplicationContext 来进行实例化,方式为 ClassUtils.forName
- 需要注意的是,实例化的类必须(间接)实现了 ConfigurableWebApplicationContext 的接口,默认的XmlWebApplicationContext 这个类是没有问题的,如果自己指定的话千万注意
/**
* @param sc current servlet context
* @return the root WebApplicationContext
* @see ConfigurableWebApplicationContext
*/
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//该方法内部将进行判断,如果你没有指定特殊的 webApplicationContext 的实例化类,则将使用Spring 默认的实例化类 org.springframework.web.context.support.XmlWebApplicationContext 来进行实例化,方式为 ClassUtils.forName
Class<?> contextClass = determineContextClass(sc);
//需要注意的是,实例化的类必须是 ConfigurableWebApplicationContext 的子类
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);
}
ContextLoader.configureAndRefreshWebApplicationContext(
org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(
对于 新创建的 webApplicationContext 对象进行配置和刷新操纵-设置 context的id、初始化 servletContext的环境变量、refresh 等等。
initPropertySources(
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
//为 servletContext 设置一个更有用的名字——而不是默认的名字,我理解的就是名字更通俗易懂
//从统一配置获取-如果你没有配置,这个就是null了
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// 默认代码会走这里,最终 servletContext 名字和 applicationContext 对象有一定关联
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
//为webApplicationContext 对象 设置 servletContext 对象
wac.setServletContext(sc);
//从统一配置获取待加载的配置文件路径,比如本文配置为 classpath:applicationContext.xml,如果没有配置,默认就是 WEB-INF/applicationContext.xml
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// 当上下文环境刷新时,webApplicationContext environment 的初始化 配置资源会被请求;这里使用卫语句判断是为了确保任何 servlet 配置资源在 refresh 之前的操作生效
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
//提前加载一切资源,比如操作系统信息,JVM信息等等
//为 sevletContext 初始化环境变量 servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//初始化所有的上下文对象,debug发现,默认的上下文对象数量为0,即该方法代码默认情况下没有起作用
customizeContext(sc, wac);
//最重要的一个方法,刷新上下文,这里是方法模板,由子类具体实现
wac.refresh();
}
StandardServletEnvironment.initPropertySources(
org.springframework.web.context.support.StandardServletEnvironment.initPropertySources(
从 getPropertySources() 可以粗略的看出加载的配置信息
[servletConfigInitParams,servletContextInitParams,jndiProperties,systemProperties,systemEnvironment]
@Override
public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
WebApplicationContextUtils.initServletPropertySources(getPropertySources(), servletContext, servletConfig);
}
servletConfigInitParams
servletContextInitParams
jndiProperties
systemProperties
systemEnvironment
ContextLoader.customizeContext(
org.springframework.web.context.ContextLoader.customizeContext(
在 上下文 refrash 之前,在上下文定义了本地配置信息后,自定义 被这个 ContextLoader 创建的ConfigurableWebApplicationContext对象(webApplicationContext 对象)
上下文初始化类可以通过全局配置参数 CONTEXT_INITIALIZER_CLASSES_PARAM 指定,默认通过这个方法获得 determineContextInitializerClasses(ServletContext),并且通过 ApplicationContextInitializer 初始化每一个被提供的 web 应用上下文。
任何 ApplicationContextInitializers 实现了 org.springframework.core.Ordered Ordered 或者被org.springframework.core.annotation.Order Order 注解标注的类会被适当的排序。
/**
* @param sc the current servlet context
* @param wac the newly created application context
* @see #CONTEXT_INITIALIZER_CLASSES_PARAM
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
*/
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
//获取上下文初始化类,,debug发现默认为0
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(sc);
for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
if (initializerContextClass != null && !initializerContextClass.isInstance(wac)) {
throw new ApplicationContextException(String.format(
"Could not apply context initializer [%s] since its generic parameter [%s] " +
"is not assignable from the type of application context used by this " +
"context loader: [%s]", initializerClass.getName(), initializerContextClass.getName(),
wac.getClass().getName()));
}
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
AbstractApplicationContext.refresh(
org.springframework.context.support.AbstractApplicationContext.refresh
这个方法太重要了,单独写一篇文章介绍。
认识 AbstractApplicationContext.refresh()