往期文章回顾:
Spring Boot 第一弹,问候一下世界!!!
前言
上弹说到如何使用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 NO_SEARCH_NAMES = Collections.singleton((Object)null);
private static final Bindable STRING_ARRAY = Bindable.of(String[].class);
private static final Bindable> STRING_LIST = Bindable.listOf(String.class);
private static final Set 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 names = isDirectory ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
names.forEach((name) -> {
this.load(nonOptionalLocation, name, profile, filterFactory, consumer);
});
});
}
getSearchLocations()方法
private Set getSearchLocations() {
Set 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 asResolvedSet(String value, String fallback) {
List 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 hobby;
private Map 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 hobby;
private Map 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 hobby;
private Map 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 hobby;
private Map 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);
}
}
结束语
感谢阅读小生文章。祝大家早日富可敌国,实现财富自由。写文不易
,一定要点赞、评论、收藏哦
,感谢感谢感谢!!!