前言
Spring提供了ApplicationContextInitializer接口作为系统初始化器的实现,其本质就是Spring容器刷新之前执行的一个回调函数,可以让我们非常方便在Spring容器刷新前,往ApplicationContext添加一些属性或者添加要激活的配置文件等。本文主要通过学习SpringBoot源码的形式,来解析一下在SpringBoot是如何创建系统初始化器并应用的。
准备
事先准备两个自定义ApplicationContextInitializer接口实现,代码如下
MyApplicationContextInitializer
less
@Slf4j
@Order(2)
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
// 声明要添加的属性
Map<String, Object> mysqlMap = new HashMap<>();
mysqlMap.put("mysql-host", "127.0.0.1");
// 将属性添加到Application Context中
applicationContext.getEnvironment().getPropertySources()
.addLast(new MapPropertySource("mysqlMap", mysqlMap));
// 添加要激活的配置文件
ConfigurableEnvironment environment = applicationContext.getEnvironment();
environment.addActiveProfile("extend");
ConfigDataEnvironmentPostProcessor.applyTo(environment);
log.info("MyApplicationContextInitializer initialize");
}
}
OtherApplicationContextInitializer
less
@Slf4j
@Order(1) // 数值越小,优先级越高
public class OtherApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
log.info("OtherApplicationContextInitializer initialize...");
}
}
Tips:这两个类之前有讲过,传送门: 教你如何扩展ApplicationContext
SpringApplicaiton启动类
less
@Slf4j
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 初始化SpringApplication
SpringApplication application = new SpringApplication(Application.class);
// 添加初始化器
application.addInitializers(new MyApplicationContextInitializer());
application.addInitializers(new OtherApplicationContextInitializer());
// 启动SpringApplication
application.run(args);
}
原理探索
调用链路
initializers列表
在调用SpringApplicaiton.ddInitializers()
注册系统初始化器时,可以看到实际就是往SpringApplication的initializers列表添加
applyInitializers()
接着查看initializers引用情况,可以看到在SpringApplication的applyInitializers()
方法中,会获取当前初始化器列表,然后逐个遍历,并调用其initialize()方法,实现在Spring容器刷新前回调。
prepareContext()
再往上查找方法调用,可以看出是在SpringApplication的prepareContext()
方法中进行初始化器回调操作的。
而prepareContext()又是在SpringApplication的run()
方法进行调用的
由此我们已经摸清了ApplicationContextInitializer的调用链路,在SpringApplication启动后不久就开始逐个遍历处理系统初始化器
。
排序
顺带一提,如果有多个ApplicationContextInitializer实现,它们之间可以通过@Order注解定义优先级,而排序则是在获取初始化器列表的时候,也就是SpringApplication的asUnmodifiableOrderedSet()
方法。
初始化链路
new SpringApplication()
搞清楚调用链路后,还需要知道this.initializers
是在什么时候?通过什么方式初始化的?
接下来再通过属性引用找到为initializers
赋值的地方
最终找到是在SpringApplication的构造函数中为其赋值的,也就是说在SpringApplication初始化的时候,就已经把ApplicationContextInitializer列表给初始化好了。
SpringApplication.getSpringFactoriesInstances()
继续往下找,最终找到是在getSpringFactoriesInstances()
方法中找到了初始化逻辑
先是获取所有ApplicationContextInitializer实现的全限定类名
然后创建具体的实例
具体创建逻辑则在SpringApplication的createSpringFactoriesInstances()
方法中,其实就是通过反射的机制创建具体实例
Tips:这个时候ApplicationContextInitializer列表并不包含自定义实现的 MyApplicationContextInitializer和OtherApplicationContextInitializer,因为这两个是在SpringApplication初始化完成后通过addInitializers()方法添加的,而这个时候还没走到addInitializers()这一步。
并且可以看到在SpringApplication的getSpringFactoriesInstances()
方法中,其实已经有做过排序AnnotationAwareOrderComparator.sort(instances);
但是在获取this.initializers时,也还是要再做一次排序list.sort(AnnotationAwareOrderComparator.INSTANCE);
因为在开发者调用SpringApplication的addInitializers()
方法添加自定义ApplicationContextInitializer后,原先已经排好序的initializers
有可能需要重新调整,所以即便在初始化initializers
时排了一次序,在获取initializers
时也再做了一次排序,也就是二次排序。
SpringFactoriesLoader
在获取所有ApplicationContextInitializer
的全限定类名时,可以看到是调用了SpringFactoriesLoader
的loadFactoryNames()
方法
可以从注释看到SpringFactoriesLoader是框架内部使用的通用工厂加载机制
,它会读取类路径下所有JAR文件中的 META-INF/spring.factories
文件 ,加载并实例化给定类型的工厂。
还简单给出了示例,spring.factories
文件必须是Key=Value形式,其中Key是抽象类或者接口的全限定类名,Value是逗号分隔的实现类的全限定类名列表。
ini
org.springframework.context.ApplicationContextInitializer=\geek.springboot.application.contextInitializer.MyApplicationContextInitializer
到这里我们就知道了,为什么在实现自动装配或者注册一些监听器时,只能按照规则在resources文件夹下创建 META-INF/spring.factories
,因为SpringFactoriesLoader
的实现就已经限定了它只会读取 META-INF/spring.factories
。并且也明白了原来所有的spring.factories
都由 SpringFactoriesLoader
读取,然后加载并实例化给定类型的实现。
loadFactoryNames()
回到loadFactoryNames()的实现,可以看到它最终是调用了loadSpringFactories()
方法获取全部已加载的Spring工厂实现,最后获取关于ApplicationContextInitializer
接口的工厂实现。
loadSpringFactories()
走进loadSpringFactories()
方法中时,可以看到SpringFactoriesLoader使用了缓存机制,根据classLoader获取相关的工厂实现,若不为空则直接返回。说明了classLoader读取META-INF/spring.factories
只会读取一次,并且是用了ConcurrentReferenceHashMap
保存工厂实现。
loadSpringFactories()
具体实现逻辑,根据classLoader查询缓存中是否存在相应的工厂实现,有的话直接返回,否则进行META-INF/spring.factories
读取
BootstrapRegistryInitializer初始化时
那么其实可以推断出在调用SpringApplicaiton的setInitializers()
方法之前,META-INF/spring.factories
已经被读取过了,回过头来看SpringApplication初始化逻辑,可以看到在setInitializers()
之前,还做过初始化this.bootstrapRegistryInitializers
操作,而就是在那个时候SpringFactoriesLoader读取了META-INF/spring.factories
触类旁通
查看SpringApplication的getSpringFactoriesInstances()
方法调用,可以看到
一些之前已经提到过的实现,跟初始化ApplicationContextInitializer
的本质其实是一样的,最终都是依靠SpringApplication的getSpringFactoriesInstances()
方法,而该方法实现又和SpringFactoriesLoader
离不开关系。
总结
从初始化到调用这完整的链路,其实在写本文之前我已经通过debug理顺了一遍。但是在写本文的时候,我并没有直接给标准答案似的,一上来就说通过SpringFactoriesLoader
加载META-INF/spring.factories
,然后怎么怎么样,最后走到SpringApplicaiton
的prepareContext()
方法进行回调。
其实我是故意的,因为若是第一次通过源码看一个功能的实现原理,其实并不会那么顺畅的,都是要通过反复断点,反复查看方法调用链路,最终才能理顺整个实现逻辑。所以本文其实是还原了当时查看源码分析原理时的视角,强调的是怎么一步步找到答案的过程。
结尾
本文章源自《Learn SpringBoot》专栏,感兴趣的话还请关注点赞收藏.