Spring启动并加载(源码解析)
一、材料
1. web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/spring.xml</param-value>
</context-param>
<!-- Spring监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2. ContextLoaderListener
package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
……
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
……
}
3.ContextLoader
package org.springframework.web.context;
public class ContextLoader {
……
private static final Properties defaultStrategies;
……
private WebApplicationContext context;
……
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
……
} else {
……
try {
if (this.context == null) {
this.context = this.createWebApplicationContext(servletContext);
}
……
return this.context;
} catch (RuntimeException var8) {
……
} catch (Error var9) {
……
}
}
}
……
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = this.determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
} else {
return (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
}
}
……
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter("contextClass");
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
} catch (ClassNotFoundException var4) {
throw new ApplicationContextException("Failed to load custom context class [" + contextClassName + "]", var4);
}
} else {
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
} catch (ClassNotFoundException var5) {
throw new ApplicationContextException("Failed to load default context class [" + contextClassName + "]", var5);
}
}
}
……
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
String configLocationParam;
……
configLocationParam = sc.getInitParameter("contextConfigLocation");
……
wac.refresh();
}
……
}
二、流程
执行顺序
web.xml 中配置的Spring监听器(ContextLoaderListener)被加载并调用上下文初始化方法(public void contextInitialized(ServletContextEvent event))
上下文初始化方法中调用父类(ContextLoader)方法(public WebApplicationContext initWebApplicationContext(ServletContext servletContext))去初始化Web应用上下文(this.initWebApplicationContext(event.getServletContext());)
现在我们的视野应该是在ContextLoader类中的initWebApplicationContext方法上,这个方法中忽略掉其他代码后,重点在于里面有一句this.context = this.createWebApplicationContext(servletContext);这句调用完后会返回一个被WebApplicationContext 接收的对象。
我简单介绍下WebApplicationContext 是什么。先参考文章了解ApplicationContext以及它的相关的实现子类
WebApplicationContext 是ApplicationContext的子接口。总之是用来接收Spring上下文对象的东西。接着,ContextLoader#createWebApplicationContext方法中做了什么事呢?它通过调用ContextLoader#determineContextClass方法确定了上下文的类并实例化该类得到对象后强转成ConfigurableWebApplicationContext返回给步骤4中的WebApplicationContext 接收。
ContextLoader#determineContextClass做了什么事呢?determineContextClass这个方法我还没细看,大体上意思是如果servletContext中找不到“contextClass”对应的类名,那就走默认策略,从defaultStrategies中取,主要在这一句上contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());一般默认情况取出来的contextClassName值是“org.springframework.web.context.support.XmlWebApplicationContext”,而XmlWebApplicationContext是从web应用下的一个或多个xml配置文件加载上下文定义,适用于xml配置方式。
现在程序已经知道要以xml配置方式去加载Spring上下文,那么只剩下如何去读取到web.xml中配置好的contextConfigLocation值得到xml链接并加载了。
ContextLoader#initWebApplicationContext方法中的this.context = this.createWebApplicationContext(servletContext);执行完后默认情况会得到这样的值,例如
接着方法中再执行到调用this.configureAndRefreshWebApplicationContext(cwac, servletContext);这句就时用来读取到web.xml中配置好的contextConfigLocation值得到xml链接并加载了。ContextLoader#configureAndRefreshWebApplicationContext这个方法中的configLocationParam = sc.getInitParameter(“contextConfigLocation”);这句获取到了web.xml中配置好的contextConfigLocation值,也就是“classpath:config/spring-mybatis.xml”,这个方法在最后的时候调用了wac.refresh();
而这个refresh方法的实现是在XmlWebApplicationContext的父类AbstractApplicationContext里面实现的。refresh里面完成了WebApplicationContext里面的beanfactory的初始化和bean载入,beanfactorypostprocessor的调用,beanpostprocessor的注册,ApplicationEvent的监听和注册,non-lazy-init的bean的初始化。
换言之,已经把该准备的都准备好了,只需要有请求来获取bean,就根据情况或返回已经初始化的bean或进行bean的Instantiation 和 Initialization。
参考
Spring-MVC理解之一:应用上下文webApplicationContext
spring容器和上下文的理解
spring源码分析之——Spring bean的加载入口
Sping提供了两种类型的 IOC 容器实现.
Spring基础篇——Spring容器和应用上下文理解
PS
“为避免误人子弟,以上仅供参考,个人见解,请配合多个相关博客食用……”