Spring MVC源码——Root WebApplicationContext

打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释.


Spring MVC 的文档一开始就给出了这样的两段示例:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
public void onStartup(ServletContext servletCxt) { // Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh(); // Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}

web.xml :

<web-app>

    <listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> <context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/app-context.xml</param-value>
</context-param> <servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet> <servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping> </web-app>

我们按照 web.xml 中的实例来看一下 Spring MVC 初始化过程.

上下文层次结构

Spring MVC 的上下文有如下这样的层级:

图中的 Servlet WebApplicationContext 是与 DispatcherServlet 绑定的上下文, 其中还有 controllers、ViewResolver、HandlerMapping 等组件.

Root WebApplicationContext 不是必须的上下文, 在需要时,可以用来在多个 DispatcherServlet 间共享一些 bean.

Root WebApplicationContext 初始化和销毁

ContextLoaderListener

web.xml 中配置的 ContextLoaderListener 用于启动和终止 Spring 的 root WebApplicationContext.

ContextLoaderListener 本身的代码十分简单:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

	public ContextLoaderListener() {
} public ContextLoaderListener(WebApplicationContext context) {
super(context);
} /**
* Initialize the root web application context.
* Servlet 上下文初始化,调用父类的方法初始化 WebApplicationContext
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
} /**
* Close the root web application context.
* Servlet 上下文被销毁,调用父类的方法销毁 WebApplicationContext
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
// 销毁 ServletContext 上实现了 DisposableBean 的属性并移除他们
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}

ContextLoader

ContextLoaderListener 直接调用了父类 ContextLoader 的方法来初始化和销毁上下文.

ContextLoaderListener 在创建上下文时,会尝试读取 contextClass context-param 来指定上下文的类型,被指定的类需要实现 ConfigurableWebApplicationContext 接口. 如果没有获取到,默认会使用 WebApplicationContext.

初始化上下文时,会尝试读取 contextConfigLocation context-param, 作为 xml 文件的路径.

初始化上下文

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.
// 保存上下文到本地实例变量中,保证上细纹能在 ServletContext 关闭时访问到
if (this.context == null) {
// 创建上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
// 如果上下文实现了 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 的属性上
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) {
// 如果有线程上下文类加载器,而且不是 ContextLoader 本身的类加载器,放入到 currentContextPerThread 中。这是一个 static 的域
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;
}
}

initWebApplicationContext() 方法调用了 createWebApplicationContext() 方法来创建上下文;调用了 configureAndRefreshWebApplicationContext() 来对实现了 ConfigurableWebApplicationContext 接口的上下文做初始化.


createWebApplicationContext() 会调用 determineContextClass() 来获取上下文类型的 Class 对象.

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 获取上下文类型
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);
} protected Class<?> determineContextClass(ServletContext servletContext) {
// 获取 serveltContext 的 'contextClass' 初始化参数。
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);
}
}
}

configureAndRefreshWebApplicationContext() 方法

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 用可以获取到的信息,获取一个更有意义的上下文
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
// 获取 ServletContext 的 'contextId' 初始化参数。
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
// 生成默认 id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 设置 servletContext 属性
wac.setServletContext(sc);
// 设置配置文件路径
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
} // The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
// 初始化属性源, 确保 servlet 属性源到位并能够在任何 refresh 之前的后期处理和初始化中使用
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
} // 在设置了配置文件之后上下文刷新之前,自定义上下文
customizeContext(sc, wac);
wac.refresh();
} protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
// 根据 ServletContext 的 'contextInitializerClasses' 和 'globalInitializerClasses' 初始化参数 加载 ApplicationContextInitializer 的 class
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(sc); for (Class<ApplicationContextInitializer<ConfigurableApplicationContext>> initializerClass : initializerClasses) {
// 获取范型参数类型
Class<?> initializerContextClass =
GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class);
// 检查 Initializer 是否适用于当前上下文对象
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()));
}
// 创建 Initializer 实例,并添加到 contextInitializers
this.contextInitializers.add(BeanUtils.instantiateClass(initializerClass));
}
// 根据 org.springframework.core.Ordered 和 org.springframework.core.annotation.Order 排序,如果没有实现或注解,会被排到最后
AnnotationAwareOrderComparator.sort(this.contextInitializers);
// 执行每个 initializer 的 initialize() 方法
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}

销毁上下文

closeWebApplicationContext() 方法如下:

public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
// 如果 context 是 ConfigurableWebApplicationContext 调用 close() 方法
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}
else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
// 移除 servletContext 中的 context 属性
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
}

Servlet 3.0+ 中初始化

Servlet 3.0+ 中可以通过 ServletContextaddlistener() 方法来添加监听器.因此可以先把 Spring 容器先创建好,再传给 ContextLoaderListener 的构造器.这里就不自己写例子了,选了单元测试中的 ContextLoaderTests.testContextLoaderListenerWithDefaultContext() 方法:

public void testContextLoaderListenerWithDefaultContext() {
MockServletContext sc = new MockServletContext("");
sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM,
"/org/springframework/web/context/WEB-INF/applicationContext.xml " +
"/org/springframework/web/context/WEB-INF/context-addition.xml");
ServletContextListener listener = new ContextLoaderListener();
ServletContextEvent event = new ServletContextEvent(sc);
listener.contextInitialized(event);
String contextAttr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
WebApplicationContext context = (WebApplicationContext) sc.getAttribute(contextAttr);
assertTrue("Correct WebApplicationContext exposed in ServletContext", context instanceof XmlWebApplicationContext);
assertTrue(WebApplicationContextUtils.getRequiredWebApplicationContext(sc) instanceof XmlWebApplicationContext);
LifecycleBean lb = (LifecycleBean) context.getBean("lifecycle");
assertTrue("Has father", context.containsBean("father"));
assertTrue("Has rod", context.containsBean("rod"));
assertTrue("Has kerry", context.containsBean("kerry"));
assertTrue("Not destroyed", !lb.isDestroyed());
assertFalse(context.containsBean("beans1.bean1"));
assertFalse(context.containsBean("beans1.bean2"));
listener.contextDestroyed(event);
assertTrue("Destroyed", lb.isDestroyed());
assertNull(sc.getAttribute(contextAttr));
assertNull(WebApplicationContextUtils.getWebApplicationContext(sc));
}

参考资料

芋道源码

Spring MVC源码——Root WebApplicationContext的更多相关文章

  1. Spring MVC源码——Servlet WebApplicationContext

    上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet ...

  2. 精尽Spring MVC源码分析 - WebApplicationContext 容器的初始化

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  3. 精尽Spring MVC源码分析 - 文章导读

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring MVC源码分析 - 一个请求的旅行过程

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  5. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  6. 精尽Spring MVC源码分析 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. spring mvc源码-》MultipartReques类-》主要是对文件上传进行的处理,在上传文件时,编码格式为enctype="multipart/form-data"格式,以二进制形式提交数据,提交方式为post方式。

    spring mvc源码->MultipartReques类-> MultipartReques类主要是对文件上传进行的处理,在上传文件时,编码格式为enctype="multi ...

  8. 精尽Spring MVC源码分析 - 调式环境搭建

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  9. 精尽Spring MVC源码分析 - MultipartResolver 组件

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

随机推荐

  1. hdu 3689 Infinite monkey theorem

    Infinite monkey theorem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/ ...

  2. C11简洁之道:tupe元祖

    tuple元组是一个固定大小不同类型的值的集合,是泛化的std::pair.我们也可以把它当作一个通用的结构体来使用,不需要创建结构体有获取结构体特征,在某些情况可以取代结构体,使程序更简洁.直观. ...

  3. [Luogu 2596] ZJOI2006 书架

    [Luogu 2596] ZJOI2006 书架 第一次指针写 FHQ_Treap(省选噩梦数据结构)AC 啦! 省选试机写它,紧张过度失败了. 省选 Day 1 考场写它,写挂了. 省选 Day 1 ...

  4. Vs2013 agent 安装

    1. 在windows 2008 R2上安装vs2013 agents需要满足: 1) .net 3.5 2) sp1补丁包(同windows7 sp1) 2. 安装vs2013 agents 步骤如 ...

  5. [uva11174]村民排队 递推+组合数+线性求逆元

    n(n<=40000)个村民排成一列,每个人不能排在自己父亲的前面,有些人的父亲不一定在.问有多少种方案. 父子关系组成一个森林,加一个虚拟根rt,转化成一棵树. 假设f[i]表示以i为根的子树 ...

  6. 聂老师的考验(反向bfs)

    题目链接:http://113.240.233.2:8081/JudgeOnline/problem.php?id=1121 这个题看起来要多次使用bfs,其实只要换个思维就会发现这就是一个简单的bf ...

  7. vue清空input file

    input file是只读的,给form一个id,用form.reset()干掉里面input的值 document.getElementById("uploadForm")&am ...

  8. Python 对象模型 -- (转)

    面向对象的纯粹性 在很久很久以前,C++还被称为面向对象语言(现在一般称为多范式通用语言),人们就对C++的面向对象的纯粹性提出了质疑,主要有以下几点: 并非所有的对象都是对象(很拗口?),比如指针本 ...

  9. poj 3751 时间日期格式转换

    题目链接:http://poj.org/problem?id=3751 题目大意:按照要求的格式将输入的时间日期进行转化. #include <iostream> #include < ...

  10. Coursera在线学习---第十节.大规模机器学习(Large Scale Machine Learning)

    一.如何学习大规模数据集? 在训练样本集很大的情况下,我们可以先取一小部分样本学习模型,比如m=1000,然后画出对应的学习曲线.如果根据学习曲线发现模型属于高偏差,则应在现有样本上继续调整模型,具体 ...