本文主要是整体启动流程请查看springboot原理(核心原理、启动流程、执行流程)中的10. 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。
image本系列源码都是基于spring-4.3.8版本之上,其他版本略有差异,但总体的核心思想相同。同时,为了使贴出的源代码尽可能的紧凑,可能会删去一些异常捕获、日志输出等代码。若文中存在纰漏错误,欢迎指正。
如上图ContextLoaderListener继承自ContextLoader又实现了ServletContextListener,
前者用于初始化Spring容器,后者用于在web容器初始化时通知自己。
由于ContextLoaderListener实现了ServletContextListener,当web容器初始化时,会调用ServletContextListener#contextInitialized,开始初始化servlet上下文监听器,ContextLoaderListener就是其中一个。
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
而ContextLoaderListener通过调用父类ContextLoader#initWebApplicationContext方法展开了Spring初始化之路。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 当前servletContext若已存在上下文,则说明已开始启动,则抛出异常,终止spring容器启动
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!");
}
// 判断当前上下文是否存在,不存在,则进行创建
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 若没有进行过特殊配置,那么该context的类型默认为XmlWebApplicationContext,具体可见createWebApplicationContext
// 且context的类型必然是ConfigurableWebApplicationContext的子类
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 该方法为spring容器真正的初始化入口
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// 将上下文设置到servletContext中,用于给其他容器获取,如SpringMvc的初始化Servlet就是通过该方法获取到父容器的
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);
}
return this.context;
}
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 创建context的第一步,即需要确定该context的类型
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) {
// 默认从servletContext中获取初始化参数,即我们web.xml中可配置的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 {
// 若没有获取到,则会以默认的策略,从本类的同级目录下以WebApplicationContext全限定名为key读取ContextLoader.properties的配置
// 最终获取到的即org.springframework.web.context.support.XmlWebApplicationContext
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);
}
}
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// 从web.xml中获取初始化参数contextId,作为当前容器的唯一ID
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
// 将当前ServletContext设置到当前容器中
wac.setServletContext(sc);
// 从web.xml中读取初始化参数contextConfigLocation,读取出的配置文件路径稍后将逐个加载
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
// 将配置文件路径设置到本容器中
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 个性化启动上下文
customizeContext(sc, wac);
/**
* 容器加载的核心方法,
* 不论是以注解方式启动的AnnotationConfigWebApplicationContext
* 还是spring-boot的启动类SpringApplication
* 最终都需要调用该方法,即AbstractApplicationContext#refresh
*/
wac.refresh();
}
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
/**
* 决定上下文需要初始化的类
* 从<init-param>中获取"globalInitializerClasses"、"contextInitializerClasses"
*/
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));
}
// 排序并逐个初始化ApplicationContextInitializer
AnnotationAwareOrderComparator.sort(this.contextInitializers);
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
由于AbstractApplicationContext#refresh方法太长,限于本篇篇幅,该方法的细节将在之后进行源码分析。
网友评论