当前位置: 首页 > news >正文

关于Spring-Boot配置加载顺序解读

一、配置加载现象

  • 加载场景说明。
  • 假设有一个特殊的场景的配置文件如下表,那么配置文件是如何生效,以及加载的顺序是怎么样的呢?有多个地方均配置了相同的参数,到底是哪一个参数项在应用中生效的呢?

配置文件

配置项

application.yaml

xxx:

yyy:

zzz: from-application.yaml

application-dev.yaml

xxx:

yyy:

zzz: from-application-dev.yaml

bootstrap.yaml

xxx:

yyy:

zzz: from-bootstrap.yaml

config-server

xxx:

yyy:

zzz: from-config-order.yaml

系统环境变量

XXX_YYY_ZZZ=from-system-environment

jvm启动参数

-Dxxx.yyy.zzz=from-jvm-args

若使用了配置中心,需增加spring-cloud配置中心配置。

二、示例代码演示

说明

版本

jdk

1.8

spring-boot

2.6.2

spring-cloud

2021.0.0

1、建立一个配置中心 config-server ,先申请好 gitee ,方便演示配置中心从 git 上获取配置项。

(1) pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.mixfate</groupId>
  <artifactId>config-server</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>config-server</name>
  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>2021.0.0</spring-cloud.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-config-server</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

(2)ConfigServerApplication.java如下:

package com.mixfate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
  public static void main(String[] args) {
    SpringApplication.run(ConfigServerApplication.class, args);
  }

}

(3) application.yaml如下:

spring:
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/xxx/spring-cloud-config
          username: xxx
          password: 123456
          default-label: master
server:
  port: 8082
logging:
  level:
    root: info
    org.springframework.cloud: debug

需在 gitee 上创建一个仓库 spring-cloud-config (名字可随意取), default-label: master 表示使用 master 分支。

2、创建一个演示项目 config-order ,演示配置文件的加载顺序。

(1) pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mixfate</groupId>
    <artifactId>config-order</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>config-order</name>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2021.0.0</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

(2)ConfigOrderApplication.java如下:

package com.mixfate;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
@SpringBootApplication
public class ConfigOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigOrderApplication.class, args);
    }
}
@Component
class InitRunner implements ApplicationRunner {
    @Value("${xxx.yyy.zzz}")
    private String name;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("xxx.yyy.zzz is " + name);
    }
}

使用了 InitRunner 演示应用初始化时输出参数的值。

(3) yaml配置文件如下:

bootstrap.yaml配置
spring:
  cloud:
    config:
      uri: http://localhost:8082
  application:
    name: config-order
  profiles:
    active: dev
xxx:
  yyy:
    zzz: from-bootstrap.yaml
application.yaml配置
server:
  port: 8085
logging:
  level:
    root: info
xxx:
  yyy:
    zzz: from-application.yaml

application-dev.yaml配置
xxx:
  yyy:
    zzz: from-applicatoin-dev.yaml

需注册在 gitee 上的仓库 spring-cloud-config 中需要建立 config-order.yaml ,即与此应用同名,启动配置中心后可使用 http://localhost:8082/config-order.yaml 访问,查看配置信息。

3、演示案例

配置环境好环境变量以及 jvm 启动参数。

运行程序可以看到以下结果 xxx.yyy.zzz is from-config-order.yaml ,说明从配置中心获取到了配置项。

接着将配置中心的配置项改掉,表示配置中心无对应的配置项,如下:

config-order.yaml:

xxx:
  yyy:
    zzz000: from-config-order.yaml

再次运行程序可以看到结果 xxx.yyy.zzz is from-jvm-args ,说明从 jvm 启动参数中获取到了,若将 jvm 参数去掉,则结果变为 xxx.yyy.zzz is from-system-environment ,说明从系统环境变量中获取到了。可以看到这个配置加载是有顺序的,可以继续尝试修改剩下的配置文件中的参数查看结果。

三、配置源码解析

idea 中运行调试逐步查看源码。

从 SpringApplication.run(ConfigOrderApplication.class, args); 开始debug,一直调试进入到 SpringApplication 类的方法 ConfigurableApplicationContext run(String... args) 中,直到 configureIgnoreBeanInfo(environment); 这一行,查看一下变量 environment ,如下图:

查看关键属性 environment 中 propertySources 的 propertySourceList ,可以看到这个 List 中初始化了9个属性来源,当然可以还有其他的,不过在此演示一些关键的配置项。同时可以看到关键属性 propertyResolver ,即属性解析器。

上面截图的 List 中要以看到配置文件的顺序是 configurationProperties -> systemProperties -> systemEnvironment -> ... -> bootstrap.yaml -> ... -> appplication-dev.yaml -> application.yaml:

接下来看看这个 PropertySourceList 是怎么拼装起来的,跟踪到方法  ConfigurableEnvironment prepareEnvironment 中,查看变量 environment 的值,如下图:

可以看到 getOrCreateEnvironment() 方法创建完成后按顺序有两个值 systemProperties 和 systemEnvironment ,接着继续 configureEnvironment(environment,applicationArguments.getSourceArgs()); 此方法后查看变量,根据变量名可以猜想到应该是属于 args 参数,于是配置上参数观察如下,其为 commandLineArgs ,且加到了第一位置。

跟踪方法 configurePropertySources 可以看到 sources.addFirst 将其添加到了第一位。

接下来是到 ConfigurationPropertySources.attach(environment); ,进到方法 attach 方法后可以跟踪到又有一个 sources.addFirst ,将其放到第一的位置。

接着跑完 listeners.environmentPrepared(bootstrapContext, environment); 后可以观察到加了几项配置,在 prepareEnvironment 方法完成后完成了基础的准备,接着是关键方法 prepareContext 中完成初始化的过程。最后的结果如下:

其中 bootstrap.yaml 配置是通过 BootstrapApplicationListener 的方法 initialize 中调用 reorderSources 完成的,可以看到是 addLast ;而 bootstrapProperties 即配置中心是通过 PropertySourceBootstrapConfiguration 的方法 initialize 中最后调用 insertPropertySources 完成的,其中的 incoming.addFirst(p); 将配置中心放到了最前面。

属性读取的过程。

前面跟着调试的代码大概了解了 propertySourceList 的拼装过程,下面看看 PropertiesResolver 是怎么解析的,同样是写一段演示代码如下,并跟踪其执行过程。

一路调试跟踪到 ConfigurationPropertySourcesPropertyResolver 类中的方法 findPropertyValue ,再跟进到 attached.findConfigurationProperty(name) ,可以方法里面的for循环里面 getSource() ,打开 Evaluate 录入可以看到 getSource() 里面的即是前一步的 propertySourceList ,如下图:

从这里就可以看出关键的点,属性是从 propertySourceList 逐个去解析的,若前面已经获取到对应的值,则后面配置的值就不会再解析了,所以这是个优先级的问题,并不是不同配置文件之间参数覆盖的过程。

前面还注意到一个点,在配置系统环境变量的时候,配置的环境变量参数为 XXX_YYY_ZZZ 而并不是 xxx.yyy.zzz ,为什么同样可以解析到呢?这个其实是一个约定的解析方法。同样是跟踪获取配置项的方法一直进入到 SpringConfigurationPropertySource 的方法 getConfigurationProperty 中,如下:

可以看到 name=xxx.yyy.zzz ,其中有个 mapper.map(name) ,注意看 mapper 为 SystemEnvironmentPropertyMapper ,继续跟踪到里面即可发现有个转换的过程 convertName ,即如下图,将 xxx.yyy.zzz 转换为 XXX_YYY_ZZZ ,所以也是可以的。

配置加载时间先后顺序的问题。

前面可以看到配置文件的加载中,若从配置中心获取到配置则需要启动到 prepareContext 时才能从配置中心读取到配置项,如果是在配置中心配置了不同的日志格式呢?是不是得在加载到配置中心后才开始使用新的日志格式呢?可以实践得到结果的确是的,如配置中心定义日志格式为 console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %m%n" ,启动日志如下:

可以看到控制台日志格式的一个变化过程。

四、总结

经过上面的分析可以得出常用配置文件中读取配置的结论:config配置中心 => jvm参数 => 系统环境变量 => 项目内的application-xxx.yaml => 项目内的application.yaml => bootstrap.yaml。

spring-boot 项目可以有多种灵活的方式设置配置项,一般来说项目内的application.yaml是一个默认配置项,再通过基本配置项对参数进行不同的赋值。

相关文章:

  • 小白量化《穿云箭集群量化》(3)量化策略编写(2)
  • 【HCSD零代码云上开发】零代码入门微信小程序和物联网
  • 博士生做科研想 idea 发现早就有人做过了,该怎么调整心态?
  • 嵌入式 - 瑞萨宣讲
  • 【云原生 | Kubernetes 系列】---Prometheus监控mysql
  • 配置MyBatis(不用Maven)
  • 1113: 递归调用的次数统计(函数专题)
  • 安全的 PHP 注销脚本
  • 小学生python编程----学爬虫
  • 如何用一个简单的内容升级心理学技巧将转化率提高了845%
  • 【LeetCode】Day126-正则表达式匹配
  • 09-排序2 Insert or Merge(浙大数据结构)
  • DPDK18.08上对VIRTIO IN ORDER 功能所做的优化
  • java毕业设计软件S2SH人力资源管理系统|人事薪资招聘oa人力请假考勤工资[包运行成功]
  • 关于C/C++中const关键字作用的一些想法
  • php的引用
  • CAP理论的例子讲解
  • CSS中外联样式表代表的含义
  • github从入门到放弃(1)
  • k个最大的数及变种小结
  • Netty 4.1 源代码学习:线程模型
  • SQLServer之索引简介
  • vue 配置sass、scss全局变量
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • ​LeetCode解法汇总2696. 删除子串后的字符串最小长度
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • $forceUpdate()函数
  • (+4)2.2UML建模图
  • (2)(2.10) LTM telemetry
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (三)elasticsearch 源码之启动流程分析
  • (十)DDRC架构组成、效率Efficiency及功能实现
  • (一)Java算法:二分查找
  • (转) RFS+AutoItLibrary测试web对话框
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)Linux下编译安装log4cxx
  • ... 是什么 ?... 有什么用处?
  • ./configure,make,make install的作用
  • ./configure、make、make install 命令
  • .NET 5种线程安全集合
  • .NET Core 实现 Redis 批量查询指定格式的Key
  • .NET Core 中的路径问题
  • .NET Core跨平台微服务学习资源
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .net 托管代码与非托管代码
  • .net反编译工具
  • @font-face 用字体画图标
  • @我的前任是个极品 微博分析
  • [APUE]进程关系(下)
  • [C++数据结构](31)哈夫曼树,哈夫曼编码与解码
  • [codevs1288] 埃及分数