likes
comments
collection
share

SpringBoot ApplicationContextInitializer系统初始化器原理解析

作者站长头像
站长
· 阅读数 16

前言

准备

事先准备两个自定义ApplicationContextInitializer接口实现,代码如下

MyApplicationContextInitializer

@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

@Slf4j
@Order(1) // 数值越小,优先级越高
public class OtherApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
       log.info("OtherApplicationContextInitializer initialize...");
    }

}

SpringApplicaiton启动类

@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列表添加

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

applyInitializers()

接着查看initializers引用情况,可以看到在SpringApplication的applyInitializers()方法中,会获取当前初始化器列表,然后逐个遍历,并调用其initialize()方法,实现在Spring容器刷新前回调。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

prepareContext()

再往上查找方法调用,可以看出是在SpringApplication的prepareContext()方法中进行初始化器回调操作的。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

而prepareContext()又是在SpringApplication的run()方法进行调用的

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

由此我们已经摸清了ApplicationContextInitializer的调用链路,在SpringApplication启动后不久就开始逐个遍历处理系统初始化器

排序

顺带一提,如果有多个ApplicationContextInitializer实现,它们之间可以通过@Order注解定义优先级,而排序则是在获取初始化器列表的时候,也就是SpringApplication的asUnmodifiableOrderedSet()方法。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

初始化链路

new SpringApplication()

搞清楚调用链路后,还需要知道this.initializers在什么时候?通过什么方式初始化的?接下来再通过属性引用找到为initializers赋值的地方

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

最终找到是在SpringApplication的构造函数中为其赋值的,也就是说在SpringApplication初始化的时候,就已经把ApplicationContextInitializer列表给初始化好了。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringApplication.getSpringFactoriesInstances()

继续往下找,最终找到是在getSpringFactoriesInstances()方法中找到了初始化逻辑

先是获取所有ApplicationContextInitializer实现的全限定类名 SpringBoot  ApplicationContextInitializer系统初始化器原理解析

然后创建具体的实例

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

具体创建逻辑则在SpringApplication的createSpringFactoriesInstances()方法中,其实就是通过反射的机制创建具体实例

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

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的全限定类名时,可以看到是调用了SpringFactoriesLoaderloadFactoryNames()方法

可以从注释看到SpringFactoriesLoader是框架内部使用的通用工厂加载机制它会读取类路径下所有JAR文件中的META-INF/spring.factories文件加载并实例化给定类型的工厂

还简单给出了示例,spring.factories文件必须是Key=Value形式,其中Key是抽象类或者接口的全限定类名,Value是逗号分隔的实现类的全限定类名列表。

org.springframework.context.ApplicationContextInitializer=\geek.springboot.application.contextInitializer.MyApplicationContextInitializer

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

到这里我们就知道了,为什么在实现自动装配或者注册一些监听器时,只能按照规则在resources文件夹下创建 META-INF/spring.factories,因为SpringFactoriesLoader的实现就已经限定了它只会读取 META-INF/spring.factories。并且也明白了原来所有的spring.factories都由 SpringFactoriesLoader读取,然后加载并实例化给定类型的实现。

loadFactoryNames()

回到loadFactoryNames()的实现,可以看到它最终是调用了loadSpringFactories()方法获取全部已加载的Spring工厂实现,最后获取关于ApplicationContextInitializer接口的工厂实现。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

loadSpringFactories()

走进loadSpringFactories()方法中时,可以看到SpringFactoriesLoader使用了缓存机制,根据classLoader获取相关的工厂实现,若不为空则直接返回。说明了classLoader读取META-INF/spring.factories只会读取一次,并且是用了ConcurrentReferenceHashMap保存工厂实现。

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

loadSpringFactories()具体实现逻辑,根据classLoader查询缓存中是否存在相应的工厂实现,有的话直接返回,否则进行META-INF/spring.factories读取

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

BootstrapRegistryInitializer初始化时

那么其实可以推断出在调用SpringApplicaiton的setInitializers()方法之前,META-INF/spring.factories已经被读取过了,回过头来看SpringApplication初始化逻辑,可以看到在setInitializers()之前,还做过初始化this.bootstrapRegistryInitializers操作,而就是在那个时候SpringFactoriesLoader读取了META-INF/spring.factories

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

触类旁通

查看SpringApplication的getSpringFactoriesInstances()方法调用,可以看到

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

SpringBoot  ApplicationContextInitializer系统初始化器原理解析

一些之前已经提到过的实现,跟初始化ApplicationContextInitializer的本质其实是一样的,最终都是依靠SpringApplication的getSpringFactoriesInstances()方法,而该方法实现又和SpringFactoriesLoader离不开关系。

总结

从初始化到调用这完整的链路,其实在写本文之前我已经通过debug理顺了一遍。但是在写本文的时候,我并没有直接给标准答案似的,一上来就说通过SpringFactoriesLoader加载META-INF/spring.factories,然后怎么怎么样,最后走到SpringApplicaitonprepareContext()方法进行回调。

其实我是故意的,因为若是第一次通过源码看一个功能的实现原理,其实并不会那么顺畅的,都是要通过反复断点,反复查看方法调用链路,最终才能理顺整个实现逻辑。所以本文其实是还原了当时查看源码分析原理时的视角,强调的是怎么一步步找到答案的过程。

结尾