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   # 类路径

跟着 debug 大哥走

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

image

第二步

image

第三步

image

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

image

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

image

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

image

第六步,进入 getSpringFactoriesInstances 方法

image

第七步,进入重载方法

image

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

image

第八步,进入查看 loadFactoryNames 方法

image

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 文件加载的。

image

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

image

image

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

image

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

image

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

image

image

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

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

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

image

image

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

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

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

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

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

image

查看 getInitializerClasses 方法

image

注意到常量 PROPERTY_NAME

image

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

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

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

image

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

image

我们进入 prepareContext 方法查看

image

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

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

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

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

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

image

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

image

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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot的执行原理包括以下几个关键步骤: 1. 启动过程:Spring Boot的启动入口是`SpringApplication`类的`run()`方法。在启动过程中,它会创建一个`ApplicationContext`(应用上下文)对象,加载所有的配置信息,并进行必要的初始化工作。 2. 自动配置:Spring Boot通过自动配置机制来简化开发者的工作。它会根据classpath中的依赖和配置文件,自动配置各种功能模块,如数据源、Web容、日志、安全等。自动配置是通过条件注解和条件属性来实现的,只有满足特定条件的配置才会生效。 3. 命令行参数解析Spring Boot支持从命令行参数中获取配置信息。它会解析命令行参数,并将其转化为Spring Boot应用的配置属性。 4. 启动Spring Boot提供了一系列的启动(starters),可以简化依赖管理。启动是一组预定义的依赖关系,可以一次性引入一组相关的依赖。例如,通过引入`spring-boot-starter-web`启动,可以自动引入与Web开发相关的依赖。 5. 条件化加载:Spring Boot可以根据条件来选择性地加载某些组件。它使用条件注解和条件属性,根据特定的条件判断是否加载某个组件。这样可以根据不同的环境和配置,动态地决定加载哪些组件。 6. 外部化配置:Spring Boot支持将配置信息外部化,可以通过配置文件、环境变量、命令行参数等方式来动态配置应用。它提供了一个统一的配置模型,并支持多种配置格式,如properties、YAML等。 7. 自定义启动过程:Spring Boot提供了一些扩展点和回调接口,可以让开发者在应用启动过程中插入自定义的逻辑。例如,可以通过实现`ApplicationRunner`和`CommandLineRunner`接口,在应用启动后执行一些初始化操作。 总体来说,Spring Boot的执行原理是通过自动配置、外部化配置和条件化加载等机制,简化了开发者的工作,使得应用可以快速启动,并按需加载所需的组件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值