Spring启动流程
我们在web服务中是怎么启动Spring容器的,我们通常是在web.xml中配置了一个ContextLoaderListener的监听器,然后设置contextConfigLocation,
1、ContextLoaderListener
这个ContextLoaderListener监听器继承了ContextLoader实现了ServletContextListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
/**
* 开始初始化WebApplicationContext
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
/**
* 关闭容器
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
}
所以当web容器启动后就会被监听到,然后调用contextInitialized方法,进而执行initWebApplicationContext方法。然而这个方法是继承于ContextLoader这个父类,一看这个类的名字就知道这就是一个上下文的加载器。
2、ContextLoader
接下来具体看下initWebApplicationContext方法的实现。而且容器的实际初始化工作是从这里开始的。
总的来说,就是干了下面几件事:
1、初始化之前检查当前ServletContext是否已经存在Spring容器;
2、使用无参构造(Java类)或者主要构造(Kotlin类)实例化1个ConfigurableWebApplicationContext实现类,默认是XMLWebApplicationContext;
3、为已经实例化的ConfigurableWebApplicationContext设置父容器,通常为null,除非自定义子类复写loadParentContext。
4、配置并刷新ConfigurableWebApplicationContext(重点);
5、以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为key存储在当前ServletContext中;
6、将ConfigurableWebApplicationContext存储在当前ContextLoader内部。
这里面最重要的方法是configureAndRefreshWebApplicationContext(cwac, servletContext) ,负责为ConfigurableWebApplicationContext设置一些属性为接下来的refresh做准备
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
/**
* 给ConfigurableApplicationContext实现类设置1个unique id
*/
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
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
/**
* 为当前ConfigurableApplicationContext设置ServletContext
*/
wac.setServletContext(sc);
/**
* 获取web.xml中的contextConfigLocation配置信息,通常是spring的xml配置文件地址
*/
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
/**
* 为当前ConfigurableApplicationContext设置contextConfigLocation配置信息(xml配置文件)
*/
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
/**
* 初始化PropertySources,一般只要容器调用refresh()方法,就会初始化PropertySources。
* 这个地方之所以现在就初始化视为了供refresh()方法执行前的 1、后处理 2、初始化 操作使用
*/
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
/**
* 自定义容器配置,在为容器设置了ConfigLocation之后,refresh之前执行
*/
customizeContext(sc, wac);
/**
* 容器刷新,加载配置好的XML、properties或者relational database schema文件
* 另外由于这个是容器启动方法,有1点需要注意:如果容器启动失败,所有已创建好的singletons将被销毁。
* 此方法有多个实现,但是主体部分是AbstractApplicationContext的refresh()
*/
wac.refresh();
}
可以看到先是为ConfigurableWebApplicationContext设置了一些属性,然后开始refresh容器
1、设置ConfigurableWebApplicationContext的unique id;
2、调用wac.setServletContext(sc),设置ServletContext为当前ServletContext;
3、获取web.xml中contextConfigLocation配置信息,并调用wac.setConfigLocation(configLocationParam)进行设置(重要)
4、提前初始化PropertySources以便在refresh前执行一些后处理或者初始化工作。
5、自定义一些容器配置,设置了ConfigLocation之后,refresh之前执行
6、上面工作完成后,开始refresh容器(很重要!很重要!很重要!)
属性设置完成,要刷新容器了,我们进入AbstractApplicationContext的refresh() 来一探究竟!
3、 AbstractApplicationContext
首先看下refresh()方法实现,这是Spring容器初始化最核心的方法,前面都只是准备工作,不管以哪种方式启动Spring容器,最终必定会调用这个方法。限于篇幅原因,本篇博客只是大致介绍refresh方法各个流程,让大家先有个印象,具体每个流程的细节将会在后续博客陆续阐述。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/**
* 刷新前的准备工作:
* 1、设置启动日期
* 2、激活flag,如closed=false,active=true
* 3、初始化PropertySources(处理配置文件中的占位符)并校验必须参数(通过ConfigurablePropertyResolver#setRequiredProperties设置)
*/
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}