likes
comments
collection
share

【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的?

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

网关项目源码 RPC项目源码 配置中心项目源码

bootstrap配置文件的读取

我们首先来了解一下springboot是如何做配置管理的。 了解了springboot对配置文件的管理,我们就能知道为什么springcloud类型的项目要使用bootstrap配置文件了。 关于SpringBoot是如何加载application和bootstrap配置文件的底层原理这里就不再次赘述了,大家可以移步到知识星球内部的如下位置进行学习。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的?

简单回忆一下,既然和配置文件相关,那么我们找到spring的run方法中的如下这行代码然后往下分析即可。

ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);

因为通过上面的学习我们知道Environment中存储了我们项目的所有配置信息。 这里我们着重分析一下这一行代码中都做了多少事情。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
    ConfigurableEnvironment environment = this.getOrCreateEnvironment();
    this.configureEnvironment(environment, applicationArguments.getSourceArgs());
    ConfigurationPropertySources.attach(environment);
    listeners.environmentPrepared(bootstrapContext, environment);
    DefaultPropertiesPropertySource.moveToEnd(environment);
    Assert.state(!environment.containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
    this.bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        EnvironmentConverter environmentConverter = new EnvironmentConverter(this.getClassLoader());
        environment = environmentConverter.convertEnvironmentIfNecessary(environment, this.deduceEnvironmentClass());
    }

    ConfigurationPropertySources.attach(environment);
    return environment;
}

首先是基于当前环境,创建一个环境对象。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 这里由于我们的项目是Web项目,所以创建的是:StandardServletEnvironment 并且这里会层层的通过extends的继承关系,不断的初始化父类。 最终,又会回到子类的实现。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 通过我们前面的了解,我们知道,其实这些代码的作用就是往容器末尾不断的添加配置文件的信息。 不过上面的创建的是系统的环境,而我们自己编写的配置文件的信息的加载,并不是在这里完成的。 我们最终读取配置文件的代码,是通过监听器的方式来完成的。

listeners.environmentPrepared(bootstrapContext, environment);

而事件监听是spring提供的一个非常重要的扩展机制,很多功能我们都可以基于监听器这种方式来实现。 我们只需要负责发布事件,对应的事件监听器就执行相应的代码来处理这个事件。 可以发现,在我们的环境创建好之后,然后就会发布一个环境预备的事件。那么此时就等待对应的监听器进行处理即可。

public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

事件发布之后,通过层层的下叠,最终,spring通过拿到所有注册的监听器的方式,让这些监听器判断当前事件是否是由自己处理的,如果是,就处理当前事件即可。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 这里我们可以知道,其实我们发布的就是一个环境预备完成的事件。 按图索骥即可。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 继续往下找,就会发现,通过迭代器的方式,会通过这些后置处理器对我们的配置文件信息进行处理。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 再早期的版本中,用的是ConfigFile进行配置文件处理。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 只不过版本高了就废弃了,而是用其他几个。 不过大致的意思也差不多。 因此,到此为止,其实我们大概就知道了,当项目流程到达这一步的时候,其实Spring做的事情就是通过IO流的方式去读取所有的配置文件信息,并且对他们进行解析。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 这里我们跳到看ConfigDataEnvironment即可 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 因此,通过上面我们可以看到,只要我们再规定的位置编写配置文件,spring就可以帮助我们去加载这些配置文件。 并且,我们也可以通过实现自己的监听器的方式,再触发对应的环境准备完毕事件之后,使用我们的监听器去处理我们的配置文件。 这里,我通过实现一下按照上面的方法,实现监听器的方式,来加载配置文件信息。 特别注意, 在 Spring Boot 中,ApplicationEnvironmentPreparedEvent事件发生在 ApplicationContext 创建之前,这意味着使用 @Component 或 @Configuration 注解的方式无法确保监听器被及时注册。 相反,我需要在应用启动时手动注册该监听器。或者使用spring.factories的方式来完成自动装配。

# Application Listeners
org.springframework.context.ApplicationListener=\
blossom.project.config.core.listener.BootstrapApplicationListener
package blossom.project.config.core.listener;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Component;

import java.io.InputStream;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: ZhangBlossom
 * @date: 2023/12/26 22:33
 * @contact: QQ:4602197553
 * @contact: WX:qczjhczs0114
 * @blog: https://blog.csdn.net/Zhangsama1
 * @github: https://github.com/ZhangBlossom
 * BootstrapListener类
 * 用于在项目启动的时候通过环境准备事件完成对bootstrap配置文件的读取加载
 * 特别注意
 * 在 Spring Boot 中,ApplicationEnvironmentPreparedEvent
 * 事件发生在 ApplicationContext 创建之前,
 * 这意味着使用 @Component 或 @Configuration 注解的方式无法确保监听器被及时注册。
 * 相反,我需要在应用启动时手动注册该监听器。
 */
//@Component
//@Configuration
//@AutoConfiguration
public class BootstrapListener
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    static {
        System.out.println("成功被加载...");
    }

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        MutablePropertySources propertySources = event.getEnvironment().getPropertySources();
        Properties properties = new Properties();
        try {
            InputStream inputStream = BootstrapListener.class.getClassLoader().getResourceAsStream("bootstrap" +
                    ".properties");
            properties.load(inputStream);
            ConcurrentHashMap<Object,Object> cache = new ConcurrentHashMap<>();
            for (Map.Entry<Object, Object> entry : properties.entrySet()) {
                cache.put(entry.getKey(),entry.getValue());
            }
            propertySources.addLast(
                    new OriginTrackedMapPropertySource("bootstrap.properties",properties)
            );
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class ConfigApplication {
    public static void main(String[] args) {

        SpringApplication app = new SpringApplication(ConfigApplication.class);
        app.addListeners(new BootstrapListener());
        ConfigurableApplicationContext context = app.run(args);
        BootstrapListener bean = context.getBean(BootstrapListener.class);
        System.out.println(bean);
        ConfigurableEnvironment environment = context.getEnvironment();

        System.out.println(environment);
    }
}

运行代码之后发现,成功了。 【BlossomConfig】SpringCloud项目是如何对bootstrap配置文件进行加载的? 当然,我还发现了另一种解决方法,就是使用@PropertySource注解。 但是使用这个注解的一个问题在于他只能解析比较常规的配置文件。对于txt这种应该是解析不了。 而且很明显,我们不应该再代码中硬编码。所以我个人比较倾向于使用监听器的方式去解析配置文件。

@Configuration
@PropertySource("classpath:bootstrap.yml")
public class PropertySourceConfig {
}

因此,我们的第一个问题,如何处理bootstrap类型的文件的问题,就已经解决了。

接下来我们可以研究一下,Nacos是如何实现配置文件的加载的,这也对我们上面分析的逻辑有确定作用。

什么是配置中心?以及如何实现一个配置中心?

SpringBoot如何实现配置的管控?

SpringCloud项目是如何对bootstrap配置文件进行加载的?

Nacos是如何实现配置文件的读取加载的?

开发配置中心前必须了解的前置知识

配置中心Server和Client端代码的编写

配置中心Core核心功能代码的编写

配置中心源码优化---本地缓存与读写锁

转载自:https://juejin.cn/post/7352414490628014089
评论
请登录