SpringBoot源码分析
- SpringBoot源码分析(1)–@SpringBootApplication注解使用和原理/SpringBoot的自动配置原理详解
- SpringBoot源码分析(2)–SpringBoot启动源码(万字图文源码debug讲解springboot启动原理)
- SpringBoot源码分析(3)–Environment简介/prepareEnvironment准备环境(万字图文源码debug分析)
文章目录
- 一、前言
- 二、Spring Boot 配置优先级
- 2.1、配置文件加载优先级
- 总结
- 2.2、如何加载外部配置文件
- 三、springboot常见的配置参数
- 3.1、命令行参数
- 3.2、System Properties与System Environment的区别
- 3.3、系统参数(System Properties)
- 3.4、系统环境参数(System Environment)
- 四、Environment读取配置属性
- 4.1、传统方式获取environment配置
- 4.2、SpringBoot2.X 引入更强大的Binder获取environment配置方式
一、前言
上一篇《SpringBoot源码分析(3)–Environment简介/prepareEnvironment准备环境(万字图文源码debug分析)》写完后还有些问题没分析完,此篇文章接着上一篇继续讲。
本文基于spring-boot-2.2.14.BUILD-SNAPSHOT源码分析Environment及配置文件加载原理。
二、Spring Boot 配置优先级
上一篇文章源码分析中有体现过配置的优先级,此处做一下总结。
以下是常用的 Spring Boot 配置形式及配置属性加载的顺序(优先级由高到低):
- 命令行参数,如
java -jar springboot.jar --name="Java项目"
- 命令行中的
SPRING_APPLICATION_JSON
指定参数, 如java -Dspring.application.json='{"name":"Java技术栈"}' -jar springboot.jar
servletConfigInitParams
初始化参数;servletContextInitParams
初始化参数;- JNDI参数(如
java:comp/env/spring.application.json
); systemProperties
Java系统参数(来源:System.getProperties()或者(-d命令行参数)
);systemEnvironment
操作系统环境变量参数;RandomValuePropertySource
随机数,仅匹配:ramdom.*
;- profile配置文件(`application-{profile}.properties(YAML)
- 配置文件(`application.properties(YAML)
- @Configuration 注解类上的 @PropertySource 指定的配置文件
- 通过SpringApplication.setDefaultProperties 指定的默认属性
以上所有形式的配置都会被加载,当存在相同配置内容时,高优先级的配置会覆盖低优先级的配置;存在不同的配置内容时,高优先级和低优先级的配置内容取并集,共同生效,形成互补配置。
我们这里说的高优先级的配置会覆盖低优先级的配置,其实原理是加载属性配置时按优先级读取配置,当读取到配置后即返回。所以给人的感觉是高优先级覆盖了低优先级。
2.1、配置文件加载优先级
我们在项目中使用最多的配置文件是application.properties、application.yml、application-test.yml等文件。此处我们讲解一下springboot加载这些配置文件的优先级。
springboot启动时默认扫描下面四个目录,优先级如下(优先级从高到低):
- file:./config/ ( 项目根路径下的config文件夹)
- file:./ (项目根路径)
- classpath:/config/ (类路径/resources下的config文件夹)
- classpath:/ (类路径/resources)
文件加载顺序如下:
- 先按优先级加载四个目录下的application文件,同一目录下propertites比yml优先。
properties > xml > yml > yaml
- 然后按优先级加载四个目录下的application-{profile}文件,同一目录下propertites比yml优先。
properties > xml > yml > yaml
注意:此处的加载顺序只是springboot加载文件时的顺序,并不是读取配置文件中属性的顺序。
配置属性加载顺序如下:
- 先按优先级加载四个目录下的application-{profile}文件,同一目录下propertites比yml优先。
properties > xml > yml > yaml
- 然后按优先级加载四个目录下的application文件,同一目录下propertites比yml优先。
properties > xml > yml > yaml
注意:此处的加载顺序才是Environment获取配置的顺序,即environment.getProperty(key)时的顺序
问题:为什么会有文件加载顺序与配置属性加载顺序两种顺序呢?
springboot默认是按照文件加载顺序,即遍历profile然后加载classpath:/,classpath:/config/,file:./,file:./config/四个目录下的properties、xml、yml、yaml文件,最后按profile为维度进行反转,所以文件加载的顺序与配置属性加载的顺序是相反的,具体原理可以看上一篇文章。
测试demo
如下图,当同时有application.propertites与application-{profile}.propertites文件时,我们可以看到属性加载时是以application-{profile}.propertites为优先的。
总结
1.加载目录优先级从高到低
file:./config/
:项目根目录找config文件夹下找配置文件。
file:./
:根目录下找配置文件
classpath:/config/
:resources下找cofnig文件夹下找配置文件。
classpath:/
:resources下找配置文件。
2.在同一个目录,application.yml和application.properties同时存在,先读取application.properties
文件优先级:properties > xml > yml > yaml
3.先读取到的配置文件,不会因为在后面其他目录再次读到同名的配置文件而被替换。以第一次读到的为准
4.有子module的工程,子module目录下的配置文件和子module目录下一层config目录的配置文件不会加载。只会加载resources下面的配置文件
2.2、如何加载外部配置文件
在实际业务使用中,我们可能遇到项目被打成jar包,然后将配置文件放在jar包外面,怎么能指定加载外部的配置文件呢?
springboot提供了一个配置属性spring.config.location,当配置了该值,则从该值中加载配置文件,否则从classpath:/,classpath:/config/,file:./,file:./config/这四个目录下加载文件。所以我们可以在启动命令中指定外部路径。
java -jar ../java/*.jar --spring.config.location=../conf/application.yml,classpath:/configs/default.properties
注意事项:支持通过命令行参数的方式指定多个配置文件,使用英文半角 , 隔开即可。
如果你通过spring.config.location指定的不是一个文件而是一个目录,在路径最后务必添加一个"/"结束,然后结合spring.config.name进行组合配置文件,组合示例如下:
# 加载/configs/application.properties 或 /configs/application.yml(默认文件名)
java -jar project.jar --spring.config.location=classpath:/configs/
# 加载/configs/custome.properties 或 /configs/custome.yml
java -jar project.jar --spring.config.location=classpath:/configs/ --spring.config.name=custome
注意事项:spring.config.name该配置参数默认值为application,所以如果只是指定了spring.config.location并为目录形式,上面示例中会自动将spring.config.name追加到目录路径后,如果指定的spring.config.location并非是一个目录,这里会忽略spring.config.name的值。
上面内容中提出了一些新的概念,如命令行参数、启动参数、系统参数、系统环境参数等等。下面就稍微了解一下。
三、springboot常见的配置参数
3.1、命令行参数
用
--key=value
的形式添加命令行参数
方式一: idea启动时添加
方式二: jar包启动时添加
java -jar ../java/*.jar --spring.config.location=../conf/application.yml --logging.config=../conf/logback-spring.xml
方式三: 启动类中添加参数
@SpringBootApplication
public class Demo3Application {
public static void main(String[] args) {
//添加命令行参数
List<String> argsList = new ArrayList<>();
argsList.add("--spring.profiles.active=sit");
if (args != null) {
argsList.addAll(Arrays.asList(args));
}
SpringApplication.run(Demo3Application.class, StringUtils.toStringArray(argsList));
}
}
3.2、System Properties与System Environment的区别
System Properties与System Environment的变量很相近,此处说明一下两者的区别。
System Environment 指的是操作系统的环境变量,而 System Properties 指的是java 程序jvm的系统变量
常见的JVM属性
3.3、系统参数(System Properties)
用
-Dkey=value
的形式添加JVM参数
方式一: 在idea启动参数中添加
方式二: jar命令启动时添加
java -jar target/xxx.jar -Dserver.port=9090 -Dserver.context-path=/test
方式三: 代码中添加
在代码里通过System.setProperty(key, value)进行设置
在java中获取jvm的系统变量代码如下:
// 获取所有的环境变量
Properties properties = System.getProperties();
// 获取指定的环境变量 如java.class.path
String property = System.getProperty("java.class.path");
3.4、系统环境参数(System Environment)
在java中获取操作系统的环境变量的代码如下:
// 获取全部的环境变量
Map<String, String> systemEnvironment = System.getenv();
// 获取某个环境变量 比如:PATH
String path = System.getenv("PATH");
四、Environment读取配置属性
4.1、传统方式获取environment配置
1.springboot启动类,传统从environment中获取值的方式environment.getProperty
@SpringBootApplication(scanBasePackages = {"com.example"})
public class Demo3Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Demo3Application.class,args);
Environment environment = run.getEnvironment();
System.out.println(environment.getProperty("server.port"));
System.out.println(environment.getProperty("people.man.name"));
}
}
2.application.yml
server:
port: 8080
people:
man:
name: 张三
age: 12
a.b.my-first-key: hello spring environment
4.2、SpringBoot2.X 引入更强大的Binder获取environment配置方式
1.先看一个简单的示例,如何使用Binder绑定environment环境变量
@SpringBootApplication(scanBasePackages = {"com.example"})
public class Demo3Application {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Demo3Application.class,args);
Environment environment = run.getEnvironment();
//此处调用Binder.get方法获取一个Binder
Binder binder = Binder.get(environment);
Integer port = binder.bind("server.port", Integer.class).get();
//bind绑定yml文件中的配置key到一个具体的对象中
People people= binder.bind("people.man", People.class).get();
System.out.println(port);
System.out.println(people.toString());
}
}
输出结果:
8080
People(name=张三, age=12)
通过以上示例,我们可以看到Binder可以很方便的动态绑定对象,类型转换。Binder还可以绑定Map,list等。我们可以发现源码中也有很多地方使用了Binder绑定配置对象,是一个非常好用强大的类。