以前学习spring
原理的时候,都是直接new
一个IOC
容器,如下:
AnnotationConfigApplicationContext aac =
new AnnotationConfigApplicationContext("com.mydemo");
那么,spring
在实际的web
项目中,是如何初始化的呢?
首先来看看我们在使用 Spring + Spring MVC
框架开发的时候是如何配置的。
在Web
容器中使用Spring MVC
,要配置web.xml
:一般会配置context-param >> listener >> fileter >> servlet
,并且执行顺序从左到右。
context-param :容器会将
<context-param>
转换为键值对,并交给servletContext管理。
listener :监听器
fileter :过滤器
配置spring
:可以看到初始化spring需要依赖于一个监听器ContextLoaderListener
<!-- 启动Spring的容器 -->
<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>
现在就来看看是怎么初始化spring
的IOC
容器的
ContextLoaderListener
首先可以看到ContextLoaderListener extends ContextLoader implements ServletContextListener
,实现了ServletContextListener
表示了它是一个监听器,并且它在web.xml
中注册了。
1.首先执行的便是contextInitialized
方法:在web
容器启动时,会触发容器初始化事件,此时ContextLoaderListener
会监听到这个事件
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
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!");
}
。。。。。。。。。
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
//创建一个springIOC容器
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置刷新ioc容器
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//把初始化完毕的ioc容器放到servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
。。。。。。。。。。。。
}
2.看一下是如何创建一个ioc
容器的
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//确定使用哪个WebApplicationContext的实现类作为spring的ioc容器
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
。。。。
}
//创建ioc容器实例
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
2.1确定使用哪个WebApplicationContext
的实现类作为spring
的ioc
容器
protected Class<?> determineContextClass(ServletContext servletContext) {
//如果在web.xml中配置了一个参数名为contextClass,值为WebApplicationContext接口实现类,
//那getInitParameter("contextClass")就会返回这个配置的实现类Class
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
。。。。。。。。
}
else {
//返回Spring默认的实现类XmlWebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
。。。。。。。。。。。。。。
}
}
//看看这个默认策略defaultStrategies是个啥玩意儿
static {
。。。。。。。。
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
。。。。。。。。。。
}
private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
ContextLoader.properties内容:
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
//发现这个默认策略是放在一个静态代码块中的,意味着ContextLoaderListener一加载的时候,就
//从配置文件ContextLoader.properties中加载了一个配置WebApplicationContext=XmlWebApplicationContext
//所以从这我们就可以知道XmlWebApplicationContext这个类就是WebApplicationContext这个接口最终的实现类,
//也是Spring启动时默认使用的类
3.为IOC
实例设置一些配置信息和创建各种bean
进入configureAndRefreshWebApplicationContext
方法
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
...
wac.setServletContext(sc);
//关键代码一:在ServletContext中获取contextConfigLocation值,就是在web.xml配置的spring配置文件的地址
//补充说明:Tomcat在加载web.xml文件时,会最终将该配置文件的配置属性参数以键值对的形式存放在每个web应用
//对应的ServletContext中,这样我们在web应用中的任何地方都可以拿到该参数值
configLocationParam = sc.getInitParameter("contextConfigLocation");
if(configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
...
//关键代码二:刷新XmlWebApplicationContext
wac.refresh();
}
refresh
方法里面就是IOC
容器初始化的大致步骤了。看过spring
源码的话,后面的都懂的了。
4.把初始化完毕的ioc
容器放到servletContext
中
//关键代码:
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
初始化后的web应用上下文被存放到了servletContext
中,具体的就是存到了一个Map
变量中,key
值就是WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
这个常量。这个key
常量在WebApplicationContext
接口中设置的:
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";