目录
1.3.1 @ConfigurationPropertie注解活动配置文件的值
1.3.3 @PropertySource和@importResource
配置文件的作用是修改Spring Boot自动配置设置的默认值。Spring Boot使用全局的固定文件名的配置文件application.properties和application.yml来修改Spring Boot自动配置的默认值。
YAML(YAML Ain’t Markup Language):YAML不是一个标记语言。以前的配置文件大多使用的是xml文件,而YAML是以数据为中心,比json\xml等更加适合做配置文件。
YAML配置文件实例:
server:
port:8080
等价于XML如下的写法:
<server>
<port>8080</port>
</server>
等价于properties的如下写法:
server.port=8080
1.YAML语法
1.1 基本语法
k:(空格)v:表示一对键值对(空格必须有);
以空格的缩进来控制层级关系;只要是左对齐的一列数据,都是同一个层级;属性和值都是大小写敏感的。
1.2 值的写法
字面量:普通的值(数字,字符串,布尔)。
k:v——字面直接来写;
字符串默认不用加上单引号或者双引号;如果加上双引号,不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思,例如name:“zhangsan \n martin”:最终会输出 zhangsan 换行 martin;如果加上单引号会转义特殊字符,特殊字符最终只是一个普通的字符串数据,例如name:‘zhangsan \n martin’会输出zhangsan \n martin
对象、Map的写法:
k:v:在下一行来写对象的属性和值的关系,需要注意缩进。
friends:
name:martin
age:18
还有一种行内写法:
friends:{name:martin,age:18}
数组(List、Set):
用‘-’表示数组中的元素:
pets:
- cat
- dog
- pig
行内写法:
pets:[cat,dog,pig]
1.3 获取YAML配置文件的值
1.3.1 @ConfigurationPropertie注解活动配置文件的值
yaml文件配置:
##键和值之间要增加空格
person:
last-name: hello
age: 18
boss: false
birthday: 2018/12/12
maps: {k1: v1,k2: v2}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 5
将yaml中的配置注入到Person.java,代码如下:
package com.martin.config.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* Person类
* 将配置文件中配置的每一个属性值,映射到这个组件中
* @ConfigurationProperties 告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
* prefix = "person":配置文件中哪个下面的所有属性进行一一映射
* 只用这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能;因此需要增加@Component注解
*
* @author xuzongxin
* @create 2019-01-03 下午 10:44
**/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boolean getBoss() {
return boss;
}
public void setBoss(Boolean boss) {
this.boss = boss;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Map<String, Object> getMaps() {
return maps;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public List<Object> getLists() {
return lists;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", age=" + age +
", boss=" + boss +
", birthday=" + birthday +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
Dog.class定义如下:
public class Dog {
private String name;
private Integer age;
}
启动测试类:
package com.martin.config;
import com.martin.config.bean.Person;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Spring Boot单元测试;
* 可以在测试期间很方便的类似编码一样进行自动注入
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootConfigApplicationTest {
@Autowired
private Person person;
@Test
public void contextLoads() {
System.out.println(person);
}
}
输出结果如下:
Person{lastName='hello', age=18, boss=false, birthday=Wed Dec 12 00:00:00 CST 2018, maps={k1=v1, k2=v2}, lists=[lisi, zhaoliu], dog=Dog{name='小狗', age=5}}
当然我们也可以在application.properties文件中配置person的信息:
# 配置person的值
person.last-name=张三
person.age=18
person.birthday=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=14
启动测试类输出结果如下:
Person{lastName='å¼ ä¸', age=18, boss=false, birthday=Fri Dec 15 00:00:00 CST 2017, maps={k1=v1, k2=14}, lists=[a, b, c], dog=Dog{name='dog', age=14}}
我们看到,输出的测试类中,lastName是一串乱码。这是因为idea对properties文件进行编辑的时候使用的是UTF-8,而properties文件在运行的时候使用的是asc码,我们打开settings进行设置,勾选Transparent native-to-ascii convetsion。
再次启动测试类,发现成功的转换了中文:
Person{lastName='张三', age=18, boss=false, birthday=Fri Dec 15 00:00:00 CST 2017, maps={k1=v1, k2=14}, lists=[a, b, c], dog=Dog{name='dog', age=14}}
1.3.2.@Value获取值和@ConfigurationProperties获取值的比较
对比项 | @ConfigurationProperties | @Value |
功能 | 批量注入配置文件中的属性 | 一个个属性的指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
松散语法是指配置名和指定注入的配置名可以“”不严格“”相同。
配置文件无论是yml还是properties他们都能获取到值;
如果说我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用@Value;
如果说我们专门编写了一个javaBean来和配置文件进行映射,我们就直接使用@ConfigurationProperties
1.3.3 @PropertySource和@importResource
1.@PropertySource:加载指定的配置文件。
比如我们的person相关的配置想要使用一个独立文件Person.properties,Person类中的属性从该配置文件中读取相应的配置。
新建person.properties文件:
# 配置person的值
person.last-name=李四
person.age=18
person.birthday=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=dog
person.dog.age=14
Person.class使用@PropertySource引入该文件:
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* Person类
*
*
* @author xuzongxin
* @create 2019-01-03 下午 10:44
**/
@Component
@ConfigurationProperties(prefix = "person")
@PropertySource(value = {"classpath:person.properties"})
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birthday;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
}
@ImportResource:导入Spring的配置文件,让配置文件里面的内容生效。
比如我们使用XML的方式创建了一个beans.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloService" class="com.martin.config.service.HelloService"></bean>
</beans>
若要使该beans.xml文件能够被Spring Boot容器加载到,我们需要使用@ImportResource导入该配置文件:
/**
* 配置启动类
*
* @author xuzongxin
* @create 2019-01-03 下午 11:20
**/
@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
public class SpringBootConfigApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootConfigApplication.class, args);
}
}
SpringBoot推荐给容器中添加组件的方式:
- 配置类——Spring配置文件
- 全注解(配置类)的方式给容器添加组件(推荐)
3.全注解方式@Bean
定义注解配置类:
import com.martin.config.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Spring配置类
*
* @author xuzongxin
* @Configuration 指明当前类是一个配置类,该配置类是用来代替之前的spring xml配置文件的
* @create 2019-01-03 下午 10:35
**/
@Configuration
public class MyAppConfig {
// 将方法的返回值添加到容器中:容器中这个组件默认的id就是方法名
// 在之前的配置文件中我们使用<bean></bean>标签添加组件
@Bean
public HelloService helloService() {
System.out.println("配置类@Bean给容器添加了helloService");
return new HelloService();
}
}
该配置类就类似于我们的配置文件,可以被Spring Boot容器加载到。
2.配置文件占位符
RandomValuePropertiesSource:配置文件中可以使用随机数,${random.value}、${random.int}、${random.int(10,100)}
属性配置占位符:可以在配置文件中使用前面配置过的属性。例如:
app.name=MyAPP
app.description=${app.name:MyApp} is a Spring Boot APP.
${app.name:默认值}来指定找不到属性时的默认值。
例如person.properties可以定义如下:
# 配置person的值
person.last-name=李四_${random.uuid}
person.age=18
person.birthday=2017/12/15
person.boss=false
person.maps.k1=v1
person.maps.k2=14
person.lists=a,b,c
person.dog.name=${person.last-name:李四}_dog
person.dog.age=14
3.Profile
Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活指定参数等方式快速切换环境。主要有以下的格式:
- 多profile文件形式:格式为application-{profile}.properties,如application-dev.properties(yml)/application-prod.properties(yml)
- yml支持的多profile文档块模式。
server:
port: 8081
spring:
profiles:
active: prod
---
server:
port: 8083
spring:
profiles: dev
---
server:
port: 8084
spring:
profiles: prod
上面的配置文件表示的是激活了prod文档块,启动Spring Boot对应的端口是8084
- 激活方式:默认使用的配置文件为application.properties,如果激活对应的配置文件可以使用以下方式,命令行如java -jar spring-boot-martin-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;application.properties中配置spring.profiles.active=dev;jvm参数-Dspring.profiles.active=dev
4.配置文件的加载位置
spring boot启动会扫描以下位置的application.properties或者application.yml作为Spring Boot的默认配置文件。
- 项目的根目录下的config目录
- 项目的根目录
- classpath下的config目录
- classpath目录
以上是按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置内容。我们也可以通过命令行启动的时候,添加配置spring.config.location来改变默认配置。例如:
java -jar spring-boot-hello-0.0.1-SNAPSHOT --spring.config.locations=C:/application.properties
5.外部配置加载顺序
1.命令行参数
所有的配置都可以在命令行上进行指定
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.context-path=/abc
多个配置用空格分开; --配置项=值
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置的random.*属性值
由jar包外向jar包内进行寻找;
优先加载带profile
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件[和jar包在同一文件夹下]
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
再来加载不带profile
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件[和jar包在同一文件夹下]
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性
6.自动配置原理
配置文件到底能够写配置哪些属性,应该怎么写,可以查看官方的使用文档:
自动配置的原理:
1.SpringBoot启动的时候加载主配置类,即添加了自动配置功能注解@EnableAutoConfiguration的类。
2.@EnableAutoConfiguration主要的作用利用org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports方法给容器导入一些组件,里面的核心代码逻辑如下:
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
该方法的核心逻辑是扫描所有jar包类路径下META-INF/spring.factories文件,从里面筛选出org.springframework.boot.autoconfigure.EnableAutoConfiguration类对应的值(D:\MAVEN\repository\org\springframework\boot\spring-boot-autoconfigure\2.0.5.RELEASE\spring-boot-autoconfigure-2.0.5.RELEASE.jar!\META-INF\spring.factories),然后加入到Spring容器中。
3)配置文件中对应的值是一个自动配置类,该类负责完成Spring Boot的自动配置功能。
4)我们以org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration类为例:
@Configuration //表示该类是一个配置类
@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的ConfigurationProperties功能,将配置文件中对应的值和HttpEncodingProperties绑定起来,并加入到Spring容器中
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)//基于Spring底层的注解@Condition,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效,否则不生效。该注解的含义是判断当前应用是否是web应用,如果是当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class)//判断当前的项目有没有CharacterEncodingFilter.class类:spring mvc中进行乱码解决的过滤器
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)//判断配置文件中是否存在某个配置 spring.http.encoding; matchIfMissing = true表示如果不存在,判断也是生效的。
public class HttpEncodingAutoConfiguration {
//该配置文件已经和SpringBoot的配置文件完成映射了
private final HttpEncodingProperties properties;
//在只有一个有参构造器的情况下,参数的值就会从容器中拿,会自动注入
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean //容器中必须没有这个组件
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
@Bean
public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
return new LocaleCharsetMappingsCustomizer(this.properties);
}
private static class LocaleCharsetMappingsCustomizer implements
WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final HttpEncodingProperties properties;
LocaleCharsetMappingsCustomizer(HttpEncodingProperties properties) {
this.properties = properties;
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (this.properties.getMapping() != null) {
factory.setLocaleCharsetMappings(this.properties.getMapping());
}
}
@Override
public int getOrder() {
return 0;
}
}
}
首先会根据不同的条件去判断当前配置类是否会生效,一旦这个配置类生效,这个配置类就会给容器中添加各种Bean组件;这些组件的属性是来自于配置文件。
5)所有在配置文件中能配置的属性都是在xxxxProperties类中封装;配置文件能配置什么就可以参考某个功能对应的这个属性类
@ConfigurationProperties(prefix = "spring.http.encoding")
public class HttpEncodingProperties {
}
@ConditionalXXX是@Conditional的派生注解,作用 是指定的条件成立的时候,才会给Spring容器添加组件。以下是Conditionnal相关的注解
自动配置类必须在一定的条件下才能生效,但是我们如何知道当前系统中有哪些自动配置类生效呢?我们可以在配置文件中,配置debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效了。