SpringBoot 深入理解配置文件加载顺序和自定义修改默认的加载顺序

1.背景

        之前对于公司统一架构的组件在安装后,有些配置项不会使用classpath下面的application.properties中的值,而是去相应组件的config/config.properties去加载,这样的好处是,本地搭建的测试环境和线程环境对于配置文件是不冲突的,极大方便的开发效率,基于这样的场景,产生了一些关于配置文件加载顺序的一系列问题,在此进行记录和学习。

2.配置文件的加载顺序

        其实关于SpringBoot配置文件加载顺序的文章有很多,这里就不详细说明了,贴出两篇大佬总结的文章:

                SpringBoot 配置文件位置的加载顺序

              【小家Spring】一篇文章彻底搞懂Spring Boot配置文件的加载顺序(项目内部配置和外部配置)

        整体的加载顺序如下图:

在这里插入图片描述

          在项目路径下面配置文件的加载顺序

在这里插入图片描述

3.深入理解

       在Spring项目中,每一个配置文件被加载后,都会对应一个JVM中的实例对象,这个实例对象会被Spring容器所管理,以上图举例config/application.properties、application.properties、classpath:application.properties、classpath:config/application.properties,都有对应的实例对象表示这个配置文件。

       所以说这些配置文件都会被Spring容器所管理,具体哪一个生效就要看前面的加载顺序了,如果我们指定了具体哪一个配置文件生效,那么他的优先级是比较高的,其他的会排在他后面生效。

       Spring会使用Environment接口的实现类管理所有的配置文件的对象,这个Environment在Spring中也是一个很重要的概念,如果你阅读Spring、SpringBoot的源码会发现,会有一步是初始化并配置Environment的代码,也就是在这里完成了对于配置文件的解析,即将配置文件对应成Java的对象,并交给Spring进行管理。

       那么此时的重点,就应该去分析在从Environment中获取配置项的value时,是获取哪一个配置文件中值就可以了,跟着environment.getProperty进行查看:

       1.实现EnvironmentAware接口,将setEnvironment方法将在Spring容器启动过程中,自动调用,然后查看environment.getProperty("component.id")的调用流程

        2.查看getProperty方法,会进入PropertyResolver接口中,因为Environment继承了PropertyResolver接口

        3.PropertyResolver接口中的String getProperty(String key),只有PropertySourcesPropertyResolver进行了实现,那么我们只需要查看相应的方法即可。

        org.springframework.core.env.PropertySourcesPropertyResolver类的getProperty方法

@Override
@Nullable
public String getProperty(String key) {
	return getProperty(key, String.class, true);
}

@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	if (this.propertySources != null) {
		//1.获取到所有的配置资源,也就是配置对象列表
		for (PropertySource<?> propertySource : this.propertySources) {
			//2.从配置对象中获取配置项value
			Object value = propertySource.getProperty(key);
			//3. 如果value为空,则进行遍历下一个配置对象
			//   如果value不为空,则进行处理,然后直接返回当前配置对象的值,不再继续遍历
			if (value != null) {

				//3.1 解析嵌套占位符,用于解析是否用占位符,并且进行转换
				if (resolveNestedPlaceholders && value instanceof String) {
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);

				//3.2 如果有必要,则进行类型转换,然后进行返回
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}

	return null;
}

        到此步我们发现,environment.getProperty("component.id")就是依次遍历所有的配置对象,如果能从这个配置对象中获取到值,那么就进行一些解析和转换后,直接进行返回了。这样的话,猜测一下也知道,肯定是this.propertySources是按照一定规则排序的,才能实现SpringBoot配置文件加载的顺序性。他的顺序性一定也是按照config/application.properties、application.properties、classpath:application.properties、classpath:config/application.properties中的顺序进行的。

         有上图和代码进行分析,我们知道如果在config/application.properties、application.properties、classpath:application.properties、classpath:config/application.properties中有配置项是重复的,那么就会找到配置对象的先后顺序进行生效和查找,只要其中一个配置对象不为空,则直接返回了。

         如果我们指定其中一个文件生效的,那么他的顺序是怎么样的呢?假设我们指定application-dev1.properties文件生效

        总结:

       1.所有的配置文件都会有对应的配置对象与之关联,并且会放到一个集合中进行管理

       2.默认情况下,如果有多个配置文件的配置项是重复的,那么就会按照SpringBoot的配置文件的加载顺序进行生效,不是说这些配置文件只有一个有用,其他的配置文件中的配置项是无用的,他们都是会生效的,只是在有配置项重复时,谁的优先级高用谁的。

 

4.自定义加载顺序

        其关键就是修改this.propertySources中配置对象的顺序,这样就可以实现他的顺序了,或者说我们想要让我们自定义目录的优先级最高,此时也只需要将这个配置对象加到集合的头部就行,关于获取这个propertySources对象可以实现ConfigurableEnvironment这个接口就行,ConfigurableEnvironment是Environment的子接口,一般我们实现EnvironmentAware接口,传递过来的也是ConfigurableEnvironment的实现类,所有我们可以将Environment强转为ConfigurableEnvironment然后进行设置。

       如下是实现的例子:

public class LoadConfigBeanFactoryPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware, Ordered {

    private static final Logger logger = LoggerFactory.getLogger(com.xxx.ocdvs.common.config.LoadConfigBeanFactoryPostProcessor.class);

    private static final String CONFIG_PROPERTIES = "config.properties";

    private ConfigurableEnvironment environment;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String configPath = getConfigPropertiesPath();
        logger.info("- Config path is {}.", configPath);
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream(configPath));
        } catch (IOException e) {
            logger.info("- Fail to load config properties because the path is error.");
        }
        environment.getPropertySources().addFirst(new PropertiesPropertySource(CONFIG_PROPERTIES, properties));
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;
    }

    @Override
    public int getOrder() {
        return -1;
    }


    /**
     * 拿到config.properties文件的目录
     */
    public String getConfigPropertiesPath(){
        return getConfPropertiesPath(CONFIG_PROPERTIES);
    }

    /**
     * 拿到/conf下properties路径,例如拿到config.properties的路径
     * @param file /conf文件夹下的.properties文件
     */
    protected String getConfPropertiesPath(String file){
        //这个路径只是举例,可以是你想指定的任何路径,我们统一架构是先安装组件,然后会根据动态的生成服务器、数据库、注册中心的地址
        //使用一个配置文件进行保存,这个文件的地址不是在tomcat目录下面
        String propertiesPath = "/config/"+file;
        return propertiesPath;
    }
}

        关键点:

        1.需要在Spring Bean生命周期实例化之前的阶段执行,也就是需要在BeanFactoryPostProcessor接口的postProcessBeanFactory方法中进行实现,关于Spring的扩展点你可能知道几个,但是对于此步骤不能在BeanPostProcessor生效之前使用。具体原因还是要自定义的加载顺序要优于Spring Bean生命周期之前,因为在Spring Bean生命周期中,会进行属性赋值,此时会从environment中获取值。

        2.将Environment强转为ConfigurableEnvironment,这样才能调用ConfigurableEnvironment.getPropertySources()获取配置对象集合进行处理。

        3.实现Ordered接口,其实不实现也行,但是实现了Ordered 接口,以后如果有两个自定义的路径,你先指定谁的优先级高,可以使用Ordered接口轻松实现。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值