spring xml配置文件加载过程
ClassPathXmlApplicationContext继承关系
其他的先不说,我们先看看常用入口ClassPathXmlApplicationContext的一个继承关系,这样大概知道了其中实现什么功能。
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/beans/beans.xml");
ctx.getBean("car");
}
看看里面涉及的一个继承关系
- Abstract抽象类的声明,将会被后面的实体类实现,并包含相关的功能;
- Interface将实现相关功能的调用;
笨方法,我们先分解每一个类,先理解一遍,再看看执行加载流程。
应用 - AbstractXmlApplicationContext
在AbstractXmlApplicationContext中做的操作就是对applicationContext.xml的解析操作;
对spring注入配置文件的解析,主要使用到BeanDefinitionReader和BeanDefinitionDocumentReader两个接口相关的实现类;
应用 - AbstractRefreshableConfigApplicationContext
/**
* Create a new AbstractRefreshableConfigApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
说明太少,看不懂,进入看看,发现就调用了父类方法的功能,最后加载了一个资源方法,实现一个资源路径的加载,大概就是一个配置环境的加载过程;
/**
* Create a new PathMatchingResourcePatternResolver.
* <p>ClassLoader access will happen via the thread context class loader.
* @param resourceLoader the ResourceLoader to load root directories and
* actual resources with
*/
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
应用 - AbstractApplicationContext
AbstractApplicationContext是Spring应用上下文中最重要的一个类,这个抽象类中提供了几乎ApplicationContext的所有操作。主要有容器工厂的处理,事件的发送广播,监听器添加,容器初始化操作refresh方法,然后就是bean的生成获取方法接口等。主要还是提供了一些方法,可以作为一个便当店前台去理解,你想吃什么便当都可以从前台拿得到。
应用 - ConfigurableApplicationContext
此接口结合了所有ApplicationContext需要实现的接口。因此大多数的ApplicationContext都要实现此接口。它在ApplicationContext的基础上增加了一系列配置应用上下文的功能。配置应用上下文和控制应用上下文生命周期的方法在此接口中被封装起来,以免客户端程序直接使用。
ConfigurableApplicationContext扩展于ApplicationContext,主要新增了两个方法 refresh()和close(),让Application具有启动、刷新、关闭应用上下文的能力。
应用 - ApplicationContext
它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持、资源访问(如URL和文件)、事件传播等方面进行了良好的支持。
与BeanFactory不同的是,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
应用 - ListableBeanFactory
ListableBeanFactory是beanFactory接口的扩展接口,它可以枚举所有的bean实例,而不是客户端通过名称一个一个的查询得出所有的实例。要预加载所有的bean定义的beanfactory可以实现这个接口来。该 接口定义了访问容器中Bean基本信息的若干方法,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。
应用 - HierarchicalBeanFactory
HierarchicalBeanFactory源码具体:
1、第一个方法返回本Bean工厂的父工厂。这个方法实现了工厂的分层。
2、第二个方法判断本地工厂是否包含这个Bean(忽略其他所有父工厂)。这也是分层思想的体现。
总结:这个工厂接口非常简单,实现了Bean工厂的分层。这个工厂接口也是继承自BeanFacotory,也是一个二级接口,相对于父接口,它只扩展了一个重要的功能——工厂分层。
应用 - BeanFactory
BeanFactory 是Spring bean容器的根接口.提供获取bean,是否包含bean,是否单例与原型,获取bean类型,bean 别名的api.
OK。有了初步的认识,下面可以深入去看看具体的流程。
ClassPathXmlApplicationContext内部流程
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent); // 初始化父类 ,获得xml路径资源解析器
setConfigLocations(configLocations); // 通过环境变量解析 xml路径
if (refresh) {
refresh(); // 这个方法时spring是最终要的一个方法,甚至体系整个ioc的声明周期
}
}
看代码发觉超级简单,只有3步,我们都理解这个是从文件中读取到bean的,从自己的角度思考一遍,就是 扫描文件->加载到代码->应用,基本也是一个这样的流程,只是考虑更多的问题,因为ioc出名就是可扩展性,所以复杂点就是这样增加了很多逻辑,加上很多都是动态加载资源,所以一时难以理解,只能说,说到某些点的时候,会返过来提一下曾经在这里加载,那现在我们来扒一扒整个流程,那先看看根据调用画了函数图。
1.super(parent)
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
这里一直执行父类方法,获取到一个资源解析器;
好吧,像是说废话。Resource大家就应该会想到DefaultResourceLoader,就是一个资源加载器,通过动态PathMatchingResourcePatternResolver继承改父类进而动态适配适合的资源解析方法。
2.setConfigLocations(configLocations)
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
里面加载一个占位符助手,循环加载多个resolvePath(locations[i]),明显,上面或者到资源解析器,这里就需要去解析xml文件, doResolvePlaceholders将执行xml配置解析
/**
* 获得系统变量解析 地址中的占位符
* Resolve the given path, replacing placeholders with corresponding
* environment property values if necessary. Applied to config locations.
* @param path the original file path
* @return the resolved file path
* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
中间会创建一个环境变量参数,后面用到,将解析后的配置文件路径设置到configLocations属性中。
ConfigurableEnvironment
1.创建PropertyResolver;
2.向PropertyResolver提供环境变量、 Java进程变量
PropertyResolver
1.创建PropertyPlaceholderHelper;
2.定义占位符的前缀和后缀(placeholderPrefix、placeholderSuffix);
3.提供getPropertyAsRawString方法给PropertyPlaceholderHelper调用,用来获取指定key对应的环境变量;
PropertyPlaceholderHelper 1.找到字符串中的占位符;
2.调用PropertyResolver.getPropertyAsRawString方法,从环境变量中取出占位符对应的值
3.用环境变量的值替换占位符;
3.refresh()
真的要吐血聊聊这个了,先上个代码吧,下个篇章慢慢扒扒这个神器。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//1:准备刷新上下文环境
prepareRefresh();
//2:获取告诉子类初始化Bean工厂 不同工厂不同实现
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//3:对bean工厂进行填充属性
prepareBeanFactory(beanFactory);
try {
// 第四:留个子类去实现该接口
postProcessBeanFactory(beanFactory);
// 调用我们的bean工厂的后置处理器. 1. 会在此将class扫描成beanDefinition 2.bean工厂的后置处理器调用
invokeBeanFactoryPostProcessors(beanFactory);
// 注册我们bean的后置处理器
registerBeanPostProcessors(beanFactory);
// 初始化国际化资源处理器.
initMessageSource();
// 创建事件多播器
initApplicationEventMulticaster();
// 这个方法同样也是留个子类实现的springboot也是从这个方法进行启动tomcat的.
onRefresh();
//把我们的事件监听器注册到多播器上
registerListeners();
// 实例化我们剩余的单实例bean.
finishBeanFactoryInitialization(beanFactory);
// 最后容器刷新 发布刷新事件(Spring cloud也是从这里启动的)
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();
}
}
}