往期文章回顾:
前言
上弹说到如何使用Spring Boot问候一下世界
,想必大家都对Spring Boot
已经有一定的掌握了。如果还没看的,没关系,可以点击上面往期回顾链接前去学习
。
今天我们一起来学习Spring Boot第二弹
,玩转Spring Boot配置文件
。
说起Spring Boot的配置文件
,真的是爱恨交加
,相对于之前Spring大量的配置文件,现在的Spring Boot简直简直简直。。。,怎一个爽字了得
。当然了,爽的同时,也迎来了不少困扰,比如:我们对于Spring Boot是如何实现
的只需要修改配置文件就能达到一定效果也是充满了好奇,这个就需要我们去阅读Spring Boot的源码-自动装配原理
了,。这里我就不再赘述了,后面我会出专门针对源码进行分析的文章
,敬请期待吧!!!
话不多说,开搞!!!
Spring Boot配置文件格式
Spring Boot 官方
提供了两种常用的配置文件格式,分别是properties
、YML
格式。相比于properties
来说,YML
更加年轻,层级也是更加分明。强烈推荐使用YML
格式
Spring Boot配置文件优先级加载机制
Spring Boot项目
启动会扫描以下位置的application.properties
或者application.yml
作为默认的配置文件.
file:./config/
file:./config/*/
file:./
classpath:/config/
classpath:/
加载的优先级顺序是从上向下加载
,并且所有的文件都会被加载
,高优先级的内容
会覆盖低优先级的内容
,形成互补配置
徒手撕源码
我们可以从
ConfigFileApplicationListener
这个类中找到,其中DEFAULT_SEARCH_LOCATIONS属性设置了加载的目录:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/";
private static final String DEFAULT_NAMES = "application";
private static final Set<String> NO_SEARCH_NAMES = Collections.singleton((Object)null);
private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
private static final Set<String> LOAD_FILTERED_PROPERTY;
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";
public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
...
...
...
}
然后在
ConfigFileApplicationListener
类中的getSearchLocations
方法中去逗号解析成Set,其中内部类Loader负责这一配置文件的加载过程
,包括加载profile指定环境的配置
,以application+’-’+name格式的拼接加载。
内部类Loader的load方法
private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
this.getSearchLocations().forEach((location) -> {
String nonOptionalLocation = ConfigDataLocation.of(location).getValue();
boolean isDirectory = location.endsWith("/");
Set<String> names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
this.load(nonOptionalLocation, name, profile, filterFactory, consumer);
});
});
}
getSearchLocations()方法
private Set<String> getSearchLocations() {
Set<String> locations = this.getSearchLocations("spring.config.additional-location");
if (this.environment.containsProperty("spring.config.location")) {
locations.addAll(this.getSearchLocations("spring.config.location"));
} else {
locations.addAll(this.asResolvedSet(ConfigFileApplicationListener.this.searchLocations, "classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/"));
}
return locations;
}
asResolvedSet()
private Set<String> asResolvedSet(String value, String fallback) {
List<String> list = Arrays.asList(StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(value != null ? this.environment.resolvePlaceholders(value) : fallback)));
Collections.reverse(list);
return new LinkedHashSet(list);
}
其实可以看出源码里面给出的
配置文件排列顺序跟加载顺序是相反的
。而是使用了Collections.reverse(list)
方法
- 我们也可以通过指定配置
spring.config.location
来改变默认配置,一般在项目已经打包后,我们可以通过以下指令来加载外部的配置
。
java -jar XXX-0.0.1-SNAPSHOT.jar --spring.config.location=F:/application.yml
- 另外也可以通过
命令行参数进行配置
所有的配置
都可以在命令行
上进行指定
多个配置用空格分开;--配置项=值
java -jar XXX-0.0.1-SNAPSHOT.jar
--server.port=8888 --server.context-path=/qlh
下面给出优先级从高到低
的配置文件排列顺序:
- 命令行参数
- 来自java:comp/env的JNDI属性
- Java系统属性(System.getProperties())
- 操作系统环境变量
- RandomValuePropertySource配置的random.*属性值
备注:由jar包外向jar包内进行寻找,优先加载带profile的,再加载不带profile的
- jar包外部的application-{profile}.properties或application.yml(带spring.profile)配- 置文件
- jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
- jar包外部的application.properties或application.yml(不带spring.profile)配置文件
- jar包内部的application.properties或application.yml(不带spring.profile)配置文件
- @Configuration注解类上的@PropertySource
- 通过SpringApplication.setDefaultProperties指定的默认属性
properties、YAML配置优先级加载机制
Spring Boot
使用一个以application命名的配置文件作为默认的全局配置文件
。支持properties后缀结尾
的配置文件或者以yml/yaml后缀结尾
的YAML的文件配置。
以设置应用端口为例初体验Spring Boot配置文件
properties后缀结尾(application.properties)
server.port=80
yml/yaml后缀结尾(application.yml/application.yaml)
server:
prot: 8088
注:同一目录下,properties配置文件优先级 > yml/yaml配置文件优先级。因此在
jar包启动时
如果带上properties写法的配置
可以覆盖配置。yaml/yml配置文件写法冒号后要加空格
properties配置文件
语法结构为:
key=value
值类型:
数字,字符串,布尔,日期
person.name=tinygrey
person.age=18
person.status=true
person.birthday=2020/11/23
对象、Map
#Map
person.assets.phone=iphone 12
person.assets.car=捷豹
#对象
person.dog.name=奶狗
person.dog.age=3
数组
person.hobby[0]=打篮球
person.hobby[1]=睡觉
person.hobby[2]=玩游戏
person.hobby[3]=学习
yml/yaml配置文件
以空格的
缩进程度来控制层级关系
。空格的个数并不重要,只要左边空格对齐则视为同一个层级
。注意不能用tab
代替空格。且大小写敏感
。支持字面值,对象,数组
三种数据结构,也支持复合 结构
。
字面值
:字符串
,布尔类型
,数值
,日期
。字符串默认不加引号,单引号会转义特殊字符。日期格式支持yyyy/MM/dd HH:mm:ss
对象
:由键值对
组成,形如key:(空格)value
的数据组成。冒号后面的空格是必须要有的,每组键值对占用一行,且缩进的程度要一致,也可以使用行内写法
:{k1: v1, ....kn: vn}
数组
:由形如-(空格)value
的数据组成。短横线后面的空格是必须要有的,每组数据占用一行,且缩进的程度要一致,也可以使用行内写法:[1,2,...n]
复合结构
:上面三种数据结构任意组合
值类型:
数字,字符串,布尔,日期
person:
name: tinygrey
age: 18
status: true
birthday: 2002/04/02
对象、Map
person:
#Map
assets:
phone: iphone 12
car: 捷豹
#对象
dog:
name: 奶狗
age: 3
#行内写法
person:
#Map
assets: {phone: iphone 12,car: 捷豹}
#对象
dog: {name: 奶狗,age: 3}
数组
person:
hobby:
- 打篮球
- 睡觉
- 玩游戏
- 学习
#行内写法
person:
hobby: [打篮球,睡觉,玩游戏,学习]
注:
YML是一种新式的格式
,层级鲜明,强烈推荐使用
。注意如下:
:
字符串
可以不加引号,若加双引号则输出特殊字符
,若不加或加单引号则转义特殊字符
数组类型
,短横线后面要有空格;对象类型
,冒号后面要有空格
yaml/yml
是以空格缩进的程度来控制层级关系,但不能用tab键代替空格
,大小写敏感
本文所有提到的配置文件都对应下面实体类做为参考
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Component
@PropertySource(value = "classpath:config/custom-profile.properties", encoding = "utf-8")
@ConfigurationProperties(prefix = "person")
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
private boolean status;
@Value("${person.birthday}")
private Date birthday;
private List<String> hobby;
private Map<String, Object> assets;
private Dog dog;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Component
@PropertySource(value = "classpath:config/custom-profile.properties", encoding = "utf-8")
@ConfigurationProperties(prefix = "dog")
class Dog {
private String name;
private Integer age;
}
配置文件如何跟实体类绑定
Spring Boot
一切的配置都是为了取值,Spring Boot提供了一些取值的方式。我们一起来看一下。@ConfigurationProperties(prefix = "person")详解
注: 该注解用于从配置文件中取值,支持复杂的数据类型,但是不支持SPEL表达式
。prefix属性:
指定获配置的前缀,毕竟配置文件中的属性很多,也有很多重名的,必须用一个前缀来区分下。
该注解可以标注在类上
也可以标注在方法上
,这里就可以看出它有两种获取值的方式
。
标注在类上
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Component //注入到IOC容器中
@ConfigurationProperties(prefix = "person")//从配置文件中读取文件
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
@Value("${person.birthday}")
private Date birthday;
private List<String> hobby;
private Map<String, Object> assets;
private Dog dog;
}
标注在方法上
/**
* @Bean 将返回的结果注入到IOC容器中
* @ConfigurationProperties 从配置文件中取值
* @return
*/
@ConfigurationProperties(prefix = "person")
@Bean
public Person person(){
return new Person();
}
综上所述
@ConfigurationProperties
注解能够轻松的让配置文件跟实体类绑定在一起。
具有以下优点:
- 注入属性支持批量,仅仅指定一个
前缀prefix
即可- 数据类型支持复杂数据,比如
List、Map
- 属性名匹配规则-
松散绑定
,比如a-b
,a_b
,aB
,A_B
都可以取值- 支持JAVA的JSR303数据校验
值得关注的是:@ConfigurationProperties
这个注解仅仅是支持从Spring Boot的默认配置文件
中取值,也就是application.properties
、application.yml
、application.yaml
,那我们如何从自定义配置文件取值呢???
别着急,有解决办法,那就是再加一个注解:@PropertySource(value = "classpath:custom-profile.properties")
,下面会有对@PropertySource
注解的介绍。请耐心往下面看。
@Value
@Value这个注解我们应该都比较熟悉了,Spring中从属性取值的注解,支持SPEL表达式,不支持复杂的数据类型,比如Map、List。使用可以参考上面实体类里面的代码。
自定义配置文件并取值
Spring Boot
在启动的时候会自动加载application.xxx
,但是有的时候为了避免application.xxx配置文件过于臃肿
,就需要我们自定义配置文件
,那么自定义配置文件的话,我们如何从自定义配置文件里面取值呢?这时候就需要配合@PropertySource这个注解
使用了。
使用@PropertySource注解
在配置类上标注@PropertySource并指定你自定义的配置文件即可。可以参考下面代码
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Component
@PropertySource(value = {"classpath:config/custom-profile.properties"}, encoding = "utf-8")
@ConfigurationProperties(prefix = "person")
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
@Value("${person.birthday}")
private Date birthday;
private List<String> hobby;
private Map<String, Object> assets;
private Dog dog;
}
对应配置文件
person.name=tinygrey
person.age=18
person.birthday=2020/11/23
person.hobby[0]=打篮球
person.hobby[1]=睡觉
person.hobby[2]=玩游戏
person.hobby[3]=学习
person.assets.phone=iphone 12
person.assets.car=捷豹
person.dog.name=奶狗
person.dog.age=3
@PropertySource注解属性
value:是一个
数组,可以指定多个配置文件
同时引入。
value是数组那么问题就来了:如果同时加载多个配置文件
,并且不同配置文件
中对同一个属性
设置了不同的值
,那么Spring会识别哪一个呢?
创建两个配置文件custom-profile.yml、custom-profile1.yml
,如下去引入。
@PropertySource(value = {"classpath:config/custom-profile.yml","classpath:config/custom-profile1.yml"})
public class Person {
...
}
我们可以通过控制变量法进行测试,具体过程我这里就不赘述了。
直接说结论
吧:Spring加载顺序
为从左到右顺序加载
,后加载的会覆盖
先加载的属性值。
另外需要注意的是:@PropertySource
默认加载xxx.properties类型
的配置文件,不能加载YML格式
的配置文件。如何解决呢?下面来解决这一问题
加载自定义YML格式的配置文件
@PropertySource
注解有一个属性factory
,默认值是PropertySourceFactory.class
,这个就是用来加载properties格式
的配置文件,那我们自定义一个用来加载YML格式
的配置文件不就可以了嘛?上代码
/**
* 解决@PropertySource只对properties文件可以进行加载,但对于yml或者yaml不能支持。
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = name != null ? name : resource.getResource().getFilename();
if (!resource.getResource().exists()) {
assert sourceName != null;
return new PropertiesPropertySource(sourceName, new Properties());
} else if (Objects.requireNonNull(sourceName).endsWith(".yml") || sourceName.endsWith(".yaml")) {
Properties propertiesFromYaml = loadYamlProperties(resource);
return new PropertiesPropertySource(sourceName, propertiesFromYaml);
} else {
return super.createPropertySource(name, resource);
}
}
private Properties loadYamlProperties(EncodedResource resource){
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return factory.getObject();
}
}
我们写好上面的代码之后,只需要添加
@PropertySource
注解中factory属性指定为YmlPropertySourceFactory
即可,代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Slf4j
@Component
@PropertySource(value = {"classpath:config/custom-profile.yml"}, encoding = "utf-8", factory = YmlPropertySourceFactory.class)
@ConfigurationProperties(prefix = "person")
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private int age;
@Value("${person.birthday}")
private Date birthday;
private List<String> hobby;
private Map<String, Object> assets;
private Dog dog;
}
对应配置文件:
person:
name: qlh
age: 22
birthday: 2012/04/02
hobby:
- 打
- 睡
- 玩
- 学
assets:
phone: iphone
car: 福特野马
dog:
name: 狗
age: 1
@PropertySource
指定加载自定义的配置文件,默认只能加载properties格式
,但是可以指定factory属性来加载YML格式的配置文件
。
同时加载多个配置
测试是否成功
编写PropertiesController
@RestController
@RequestMapping("properties")
@Slf4j
public class PropertiesController {
final
Person person;
public PropertiesController(Person person) {
this.person = person;
}
@GetMapping("getProperties")
public Dict getProperties(){
log.info(person.toString());
return Dict.create().set("person", person);
}
}
浏览器输入:
http://localhost:8081/springboot-properties/properties/getProperties
验证结果。看到打印类信息,表示加载自定义YML格式的配置文件
成功了。
扩展功能
SpringBoot
还提供了@ImportResource
注解加载外部配置文件,只不过@ImportResource
通常用于加载Spring的xml配置文件
@ImportResource使用
Spring Boot
提出零xml的配置
,因此Spring Boot默认情况
下是不会主动识别
项目中Spring的xml配置文件
。为了能够加载xml的配置文件
,Spring Boot提供了@ImportResource注解
,该注解可以加载Spring的xml配置文件
,通常加于启动类
上。这里就不做赘述了,代码参考下面。
//value:Spring的xml配置文件,支持多个。
@ImportResource(value = {"classpath:config/beans.xml"})
@SpringBootApplication
public class SpringbootPropertiesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPropertiesApplication.class, args);
}
}
Spring Boot多环境配置
多环境配置有什么好处呢???
不同环境配置可以配置不同的参数
便于部署,提高效率,减少出错
yml多环境配置
application.yml
主配置文件
server:
port: 8081
servlet:
context-path: /springboot-properties
#配置激活选项
spring:
profiles:
active: dev
application-dev.yml
开发配置文件
#指定属于哪个环境
spring:
profiles:
- dev
application-prod.yml
生产配置文件
#指定属于哪个环境
spring:
profiles:
- prop
application-test.yml
测试配置文件
#指定属于哪个环境
spring:
profiles:
- test
properties多环境配置
(1)主配置文件:配置激活选项
spring.profiles.active=dev
(2)其他配置文件:指定属于哪个环境(同yml,只不过表现形式是key=value
的,三个配置文件分别是:application-dev.properties
,application-prod.properties
,application-test.properties
)
yml多环境配置和properties多环境配置比较
Properties配置多环境
:需要添加多个配置文件yml配置多环境
:可添加多个配置文件,可不添加,使用---
分隔(案例如下面代码)(不建议使用该方法,这样显得配置文件臃肿,强烈建议添加多个配置文件
,也不费事。)
server:
port: 8081
servlet:
context-path: /springboot-properties
spring:
profiles:
active: dev
---
spring:
profiles:
- test
---
spring:
profiles:
- prod
---
spring:
profiles:
- dev
一般使用的配置文件
application.yml
:是主配置文件,放一些项目通用的配置
application-dev.yml
:放平常开发的一些配置,比如说数据库的连接地址、帐号密码等
application-prod.yml
:放生产环境的一些配置,比如说数据库的连接地址、帐号密码等
application-test.yml
:放测试环境需要用到的参数
激活指定profile
使用spring.profiles.active激活
无论是使用上述多文档块
的方式,还是新建application-test.yml
文件,都可以在配置文件中指定 spring.profiles.active=test
激活指定的profile。
打成jar包运行时候使用命令行激活
java -jar XXXX-0.0.1-SNAPSHOT.jar --spring.profiles.active=test
使用虚拟机参数激活
-Dspring.profiles.active=test
在java代码中激活
@SpringBootApplication
public class SpringbootPropertiesApplication {
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "test");
SpringApplication.run(SpringbootPropertiesApplication.class, args);
}
}
结束语
感谢阅读小生文章。祝大家早日富可敌国,实现财富自由。写文不易
,一定要点赞、评论、收藏哦
,感谢感谢感谢!!!