目录
1.方法 initWebApplicationContext() 位于contextLoader 类中
1.1 createWebApplicationContext(servletContext)
1.2 configureAndRefreshWebApplicationContext(cwac, servletContext)
1.2.2 customizeContext(sc, wac)
1.2.2.1 determineContextInitializerClasses(sc)
跳转 AbstractApplicationContext .refresh() 继续
1.方法 initWebApplicationContext() 位于contextLoader 类中
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 先判断当前servlet 中是否存ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE根容器标识
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// 1.默认会实例化一个根容器
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//2.为创建的root 容器设置父上下文 一般父上下文为空
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//3.配置容器并刷新
configureAndRefreshWebApplicationContext(cwac, 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) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
//东方鲤鱼
首先先判断了当前servlet中是否已经有了名为ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 的容器,不为空抛出异常提示;
1.1 createWebApplicationContext(servletContext)
如果为空 默认创建一个根容器, spring 会生成一个父级ioc容器,就是这个方法,但是在后边的代码中会看到真正的ioc 容器;标识1 处 的 createWebApplicationContext(servletContext)方法内部实现如下
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// 获得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() + "]");
}
// 生成WebApplicationContext 的对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
//东方鲤鱼
类继承关系图如下
ConfigurableWebApplicationContext 接口:同时继承 WebApplicationContext 和 ConfigurableApplicationContext 的 接口
WebApplicationConext 接口:继承了ApplicationContext 接口,为 web 应用 提供 配置(获取 ServletContext)的接口,当应用正在运行时,是只读的,但是如果实现类支持的话,可以重新加载;相对于其父接口的 ApplicationContext ,该接口提供了 获取 ServletContext 对象的方法;
ConfigurableApplicationContext 接口:ApplicationContext 的子类,提供 配置 一个 应用上下文的 属性,如设置 environment,BeanFactoryPostProcessor,ApplicationListener,ProtocolResolver 等;
1.1.1 determineContextClass()
在determineContextClass()方法中 经过两个判断来返回context 类的类信息,代码如下
protected Class<?> determineContextClass(ServletContext servletContext) {
//获取web.xml 中配置的属性 一般不会自定义
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 {
// 默认使用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);
}
}
}
// 东方鲤鱼
第一次判断,从 ServletContext 获取配置的 context 类;
第二个判断,从 ContextLoader.properties 获取配置的 context 类。
默认情况下,我们不会主动在 ServletContext 中配置 context 类,所以基本是使用 ContextLoader.properties 配置的 context 类,即 XmlWebApplicationContext 类。
回到initWebApplicationContext方法的标识第二步 ,为根容器设置一个父类,这个是spring 默认的,一般web应用的父类都为null
两次判断决定context 的类型,
1.2 configureAndRefreshWebApplicationContext(cwac, servletContext)
接着调用标识3 方法configureAndRefreshWebApplicationContext(cwac, servletContext) 来完成配置何容器的初始化,方法内部实现如下:
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
// 如果上下文使用了默认id 重新配置
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
//从web.xml 中获取
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()));
}
}
wac.setServletContext(sc);
// 从servlet获取配置文件
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//抽象方法 实现在AbstractApplicationContext
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 初始化自定义配置
customizeContext(sc, wac);
//webApplicationContext刷新
wac.refresh();
}
//东方鲤鱼
1.2.1 getEnvironment ()
解释:ConfigurableWebApplicationContext . getEnvironment 方法为抽象方法; AbstractApplicationContext . getEnvironment() 为方法的实现:依次调用了父类构造方法getEnvironment 方法 最终返回 StandardServletEnvironment 实现类,这里发生了子类重写父类的方法,其中的 propertySource 字段(MutablePropertySources 类)中初始化了 5 个 PropertySource(目前前 3 个 source 都是 占位符 object,后期会进行填充和替换);即完成了 Environment 相关实现类的 初始化;
依赖图:
这里其实发发生了子类重写父类方法的过程,StandardServletEnvironment 类 继承StandardEnvironment 类,也有和父类customizePropertySources()方法;我们点击wac.getEnvironment() 进入ConfigurableApplicationContext 类 发现有一个私有方法 new 了一个 StandardEnvironment 对象; 这很好理解 ,真正的实现就在 StandardEnvironment 类;
父类StandardEnvironment 类
下图先看父类StandardEnvironment 类;
子类StandardServletEnvironment
这里只有两个资源属性,再看子类中 下图
一共生成了五个资源配置属性;
我们利用断点模式看一下生成那些环境资源属性:
1.2.2 customizeContext(sc, wac)
接着完成自定义配置的初始化,进入customizeContext(sc, wac)方法,如下
protected void customizeContext(ServletContext sc, ConfigurableWebApplicationContext wac) {
// 1.获取到自定义的类
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> initializerClasses =
determineContextInitializerClasses(sc);
//2.循环判断泛型
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));
}
//3.对实现了order接口的类排序
AnnotationAwareOrderComparator.sort(this.contextInitializers);
//4.按优先级顺序初始化
for (ApplicationContextInitializer<ConfigurableApplicationContext> initializer : this.contextInitializers) {
initializer.initialize(wac);
}
}
//东方鲤鱼
1.2.2.1 determineContextInitializerClasses(sc)
解释:第一步,从web.xml 中解析配置参数,主要是自定义的配置类,determineContextInitializerClasses(sc)方法如下:
protected List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>>
determineContextInitializerClasses(ServletContext servletContext) {
List<Class<ApplicationContextInitializer<ConfigurableApplicationContext>>> classes =
new ArrayList<>();
// 从web.xml 中找全局globalInitializerClasses的属性
String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
if (globalClassNames != null) {
// 分割字符串
for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
// 多个的情况 循环反射拿到配置类信息放到集合
classes.add(loadInitializerClass(className));
}
}
// 从web.xml 中找CONTEXT_INITIALIZER_CLASSES_PARAM参数 的属性
String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
if (localClassNames != null) {
for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
classes.add(loadInitializerClass(className));
}
}
return classes;
}
主要操作就是从web.xml 配置中解元素为globalInitializerClasses,contextInitializerClasses的属性,切割字符串后利用反射拿到类信息返回;一般情况这两个属性都不会配置,为null,所以不进断点;
此时我们再看webApplicationContext 对象,第一步的判断就是为了重新设置id 属性,而 wac.setConfigLocation(configLocationParam); 为 ConfigLocations 设置了值,见下图
第二步:对返回的自定义配置类集合循环判断其泛型是否为实现类,并设置到父级容器上下文中,
第三步:对实现了order 接口的类进行排序,
第四步:安装优先级顺序调用初始化方法
到此父级容器的配置初始化实例化完成,需要执行refresh()方法来刷新,这个方法中做了spring中的很多操作,ConfigurableApplicationContext . refresh 方法为抽象方法,AbstractApplicationContext .refresh(): 所有的 ApplicationContext 的唯一实现方法,采用了 模版方法设计模式;
跳转 AbstractApplicationContext .refresh() 继续