转载

【戏说Spring Boot】Spring Boot初始化器解析

文章首发于公众号,欢迎订阅

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

今天,我们来讨论一下 Spring Boot 初始化器的执行过程。

Spring Boot 有三种方式定义初始化器,下面逐一分析。

1、定义在 spring.factories 文件中,被 SpringFactoriesLoader 发现注册(工厂加载机制)

首先我们自定义一个类实现 ApplicationContextInitializer

public class DemoInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        Map<String, Object> map = new HashMap<>();
        map.put("demo", 1);
        MapPropertySource mapPropertySource = new MapPropertySource("demoInitializer", map);
        environment.getPropertySources().addLast(mapPropertySource);
        System.out.println("run demoInitializer");
    }
}

然后在 resource 目录下面新建 /META-INF/spring.factories 文件,并写入如下一行配置。

org.springframework.context.ApplicationContextInitializer=com.example.initializer.DemoInitializer   # 类路径

【戏说Spring Boot】Spring Boot初始化器解析

跟着 debug 大哥走

【戏说Spring Boot】Spring Boot初始化器解析

第一步,让我们从主函数进入,一窥究竟

【戏说Spring Boot】Spring Boot初始化器解析

第二步

【戏说Spring Boot】Spring Boot初始化器解析

第三步

【戏说Spring Boot】Spring Boot初始化器解析

第四步,进入 SpringApplication 的构造方法

【戏说Spring Boot】Spring Boot初始化器解析

第五步,进入重载构造方法

【戏说Spring Boot】Spring Boot初始化器解析

setInitializerssetListeners ,我们猜测,Listener 会不会也和 Initializer 一样,也是采用 spring.factories 的方式来注册的。

【戏说Spring Boot】Spring Boot初始化器解析

第六步,进入 getSpringFactoriesInstances 方法

[图片上传失败...(image-e916cb-1587300696695)]

第七步,进入重载方法

【戏说Spring Boot】Spring Boot初始化器解析

我们在最后打个断点看看,卧槽,发现第一次到这里,初始化器已经成功获取了,有点意思!

【戏说Spring Boot】Spring Boot初始化器解析

第八步,进入查看 loadFactoryNames 方法

【戏说Spring Boot】Spring Boot初始化器解析

loadFactoryNames 方法调用了 loadSpringFactories 方法来获取配置信息。

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 1.查找缓存
    MultiValueMap<String, String> result = cache.get(classLoader);
    // 2.缓存存在,直接返回
    if (result != null) {
        return result;
    }

    try {
        // 3.缓存不存在,读取指定资源文件
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            // 4.构造Properties对象
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 5.获取key对应的value
                String factoryClassName = ((String) entry.getKey()).trim();
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    // 6.逗号分割value
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        // 7.保存结果到缓存
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

从上我们发现一个常量 FACTORIES_RESOURCE_LOCATION ,而它的值就是 META-INF/spring.factories

整个 loadSpringFactories 过程如下:

  1. 查找缓存,缓存存在,直接返回,否则继续
  2. 缓存不存在,读取指定资源文件
  3. 构造 Properties 对象
  4. 获取 key 对应的 value
  5. 逗号分割 value
  6. 保存结果到缓存

实际上,我们下面要谈到的 DelegatingApplicationContextInitializer ,也是由 Spring Boot 内置的 spring.factories 文件加载的。

【戏说Spring Boot】Spring Boot初始化器解析

第九步,代码又回到 getSpringFactoriesInstances 方法,此时调用 createSpringFactoriesInstances 方法,利用反射依次实例化结果对象

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

第十步,又回到 getSpringFactoriesInstances 方法,此时对结果对象排序

【戏说Spring Boot】Spring Boot初始化器解析

就是在初始化器上标注的 @Order 注解中的数字,0 表示优先级最高。

【戏说Spring Boot】Spring Boot初始化器解析

第十一步,整个 getSpringFactoriesInstances 方法执行完,此时这些初始化器全部初始化成功,且被 Spring 管理

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

实际上,这是一种 Service Provider Interface (服务发现机制),它通过在 ClassPath 路径下的 META-INF 文件夹查找文件,自动加载文件里所定义的类。比如在 Dubbo、JDBC、Tomcat 中都使用到了 SPI 机制。

2、 SpringApplication 初始化完成后手动添加

只需要如下三行代码即可。

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

可以看到,实际上和方式一最终调用的 setInitializers 方法是一样的,这个相当简单,直接添加。

【戏说Spring Boot】Spring Boot初始化器解析

3、定义成环境变量,被 DelegatingApplicationContextInitializer 所发现注册(默认优先级最高)

这种方式和第一种的相似,只不过这里不用 spring.factories 这种形式,而是直接在配置文件中加入下面一行。

context.initializer.classes=com.example.initializer.DemoInitializer

【戏说Spring Boot】Spring Boot初始化器解析

首先进入 DelegatingApplicationContextInitializer ,查看如下方法

【戏说Spring Boot】Spring Boot初始化器解析

查看 getInitializerClasses 方法

【戏说Spring Boot】Spring Boot初始化器解析

注意到常量 PROPERTY_NAME

【戏说Spring Boot】Spring Boot初始化器解析

这也是为什么我们可以在配置文件中写入上述名称,不过写的时候是没有任何提示的。从配置文件中获取到对应的初始化类信息,然后执行初始化方法。同样配置文件中的类名以逗号分割,来获得每个所需的系统初始化器的全限定类名。

【戏说Spring Boot】Spring Boot初始化器解析

Q:初始化器是何时被调用的,也就是何时执行 initialize 方法的?

实际上在上面 debug 的过程中,我带着大家一直在看 SpringApplication 的构造初始化方法,然而最后还有一步 run 方法没有分析。

【戏说Spring Boot】Spring Boot初始化器解析

run 方法代码过多, 我截取一部分

【戏说Spring Boot】Spring Boot初始化器解析

我们进入 prepareContext 方法查看

【戏说Spring Boot】Spring Boot初始化器解析

没错,应该就是这个了,再点进去看一下

【戏说Spring Boot】Spring Boot初始化器解析

【戏说Spring Boot】Spring Boot初始化器解析

这不,首先获取了实现 ApplicationContextInitializer 接口的实现类,然后分别调用实现类各自的 initialize 方法。

【戏说Spring Boot】Spring Boot初始化器解析

Q:为什么 DelegatingApplicationContextInitializer 加载的初始化器是优先于其他方式执行呢?

DelegatingApplicationContextInitializer 的 order 为 0,因此优先级最高,会被最先加载。

我们可以从它的类定义中看到

【戏说Spring Boot】Spring Boot初始化器解析

所以我们在上面 debug 时才会看到 DelegatingApplicationContextInitializer 是排在第一的。

【戏说Spring Boot】Spring Boot初始化器解析

如果你给自定义的初始化器的 order 赋值为 0,那么自定义初始化器就是第一个了。

好了,下面该轮到你们表演了!

【戏说Spring Boot】Spring Boot初始化器解析

思考题

  • 谈谈你对 SpringFactoriesLoader 的理解, SpringFactoriesLoader 是如何加载工厂类的?
  • 系统初始化器的作用(可以结合系统自带的初始化器的作用)。
  • 系统初始化器何时调用?
  • 如何实现自定义系统初始化器?
  • 自定义系统初始化器有没有什么注意事项?

答案我们下期给出!

【戏说Spring Boot】Spring Boot初始化器解析

我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!

【戏说Spring Boot】Spring Boot初始化器解析

原文  https://segmentfault.com/a/1190000022422694
正文到此结束
Loading...