1.配置文件
Spring Boot使用一个全局的配置文件,配置文件名称是固定的:application.properties或者application.yml。如果两个文件同时存在,那么application.properties的优先级更高。最好不要混着用,用一种就可以了。
配置文件放在src/main/resources目录或者类路径/config下。配置文件的作用:修改Spring Boot自动配置的默认值。
.yml是YAML语言的文件,以数据为中心,比json、xml等更适合做配置文件。
YAML参考语法规范:https://yaml.org/。
YAML:
server:
port: 8081
XML:
<server>
<port>8081</port>
</server>
2.YAML语法
1.基本语法
k:(空格)v:表示一对键值对(空格必须存在)。
YAML是以空格的缩进来表示层级关系,就像Python中用缩进表示语句块,在YAML里,空格的多少没什么要求,这里不能用tab,只要左对齐的都看错同一个层级的。
属性和值是大小写敏感的。
2.值的写法
字面量:普通的值(数字,字符串,布尔)
k: v:字面量直接书写,字符串默认不用加双引号或单引号,如果加了引号,表示不同的意思。字符串可以写多行,从第二行开始,必须有一个单空格缩进,换行符会被转换为空格。
key: "a \n b":此时value是a 换行 b。
key: 'a \n b':此时value是a \n b。
对象、Map:
k: v:在下一行书写对象的属性和值的关系,注意要用缩进。
friend:
name: zhangsan
age: 18
行内写法:
friend: {name: zhangsan, age: 18}
数组(List、Set):
用- 值表示数组中的一个元素。
pets:
- cat
- dog
- pig
行内写法:
pets: [cat, dog, pig]
数组和对象可以嵌套使用构成符合结构。
person:
name: 'zhangsan \n'
username: 张三
age: 18
pet:
name: 小狗
gender: male
animal:
- dog
- cat
- fish
interest: [足球, 篮球]
friends:
-
- zhangsan
is
my
best
friend
- "lisi\n"
child:
- name: xiaozhang
age: 18
- name: xiaoli
pet:
- a
- b
- {name: lisi, age: 18}
与其对应的Java对象可以写成这样:
class Person {
String name;
String username;
int age;
Map<String, Object> pet;
List<String> animal;
List<String> interest;
List<Object> friends;
List<Map<String, Object>> child;
}
通过这个网址:https://nodeca.github.io/js-yaml/,可以将YAML转换成js对象的形式,js对象看起来更加熟悉。
3.配置文件值注入
创建application.yml文件,写入一些属性。创建Person类和Dog类,写入属性值和set方法以及toString()方法。
person:
lastName: hello
age: 18
boss: false
birth: 1999/11/22
maps: {k1: v1, k2: 12}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 12
Person类在写@ConfigurationProperties的时候,会报错,因为有一个包没有导入,需要在pom.xml中加入下面的依赖即可。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
package com.atguigu.springboot.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;
// @ConfigurationProperties:告诉Spring Boot将本类中所有属性和配置文件中相关配置进行绑定
// prefix = "person":确定和配置文件中的哪个进行一一映射
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private int age;
private boolean boss;
private Date birth;
private Map<String, Object> maps;
private List<Object> lists;
private Dog dog;
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setAge(int age) {
this.age = age;
}
public void setBoss(boolean boss) {
this.boss = boss;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public void setMaps(Map<String, Object> maps) {
this.maps = maps;
}
public void setLists(List<Object> lists) {
this.lists = lists;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"lastName='" + lastName + '\'' +
", age=" + age +
", boss=" + boss +
", birth=" + birth +
", maps=" + maps +
", lists=" + lists +
", dog=" + dog +
'}';
}
}
package com.atguigu.springboot.bean;
import org.springframework.stereotype.Component;
@Component
public class Dog {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
然后打开test文件夹下的测试类,如下:
package com.atguigu.springboot;
import com.atguigu.springboot.bean.Person;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBoot02ConfigApplicationTests {
@Autowired
Person person;
@Test
void contextLoads() {
System.out.println(person);
}
}
运行contextLoads()方法,就可以在控制台看到person对象的输出,而且person对象的值都是从application.yml中取的。
如果提示Person不能自动注入,检查Person上是否带了@Component,检查主启动类和Person类的关系,保证Person类要被扫描到才行,再检查下主启动类和测试类的包名是否一致(我就是卡在了包名不一致上,导致Person自动注入失败)。
根据输出结果查看是否符合预期,如果有值为null的,检查是否有set方法,检查application.yml文件中的书写是否正确。
1.修改properties配置文件在idea中默认编码为utf-8
同样,还可以在application.properties中写配置内容,内容如下:
person.lastName=张三
person.age=18
person.birth=1999/11/22
person.maps.k1=v1
person.maps.k2=12
person.lists=a,b,c
person.dog.name=小狗
person.dog.age=12
再次运行测试类,会发现lastName乱码了,这时候,需要对properties文件设置一下编码,再次运行就正常了。
2.@Value获取值和@ConfigurationProperties获取值比较
在给属性赋值的时候,我们可以采用@ConfigurationProperties从配置文件中获取数据,也可以使用@Value来填充值。
@Value支持3种写法:
- 普通字面量:@Value("字面量")
- 从环境变量或配置文件中获取值:@Value("${key}")
- 通过Spring Expression Language获取值,可以做一些简单的运算:@Value("#{1+3+5}")
Spring Boot支持松散绑定。松散绑定的意思就是说,在绑定的时候,“first-name”、“firstName”、“first_name”、“FIRSTNAME”都看作是一样的,都能完成值的绑定,有点类似于equalsIgnoreCase()的感觉。
JSR303数据校验:给需要校验的类加上@Validated注解,给需要校验的字段加上相应的注解即可。
| @ConfigurationProperties | @Value |
功能 | 批量注入配置文件中的属性 | 需要对每个属性单独写 |
松散绑定(松散语法) | 支持 | 不支持 |
Spring Expression Language | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
我们可以根据具体的应用场景,确定使用哪种方式来注入值。
如果我们写了一个JavaBean,这个JavaBean就是为了和配置文件中的值一一映射,一定是使用@ConfigurationProperties,因为它使用起来更加方便。
如果我们在某个方法中,只需要单独获取一下配置文件中的某个值,或者需要根据这个值做一下简单的运算,可以考虑使用@Value的方式。
3.@PropertiesSource、@ImportResource、@Bean
@PropertiesSource:加载指定的配置文件
@ConfigurationProperties注解默认会从全局配置文件中获取值,如果有很多的配置文件都写在一个properties里,就不方便了,此时,需要将properties进行分类,同类型的放在一起,便于维护。
这里,假设把person相关的属性都拿到person.properties配置文件中,为了能让@ConfigurationProperties注解依旧可以读取到person.properties中的值,我们需要加一个@PropertySource(value ={"classpath:person.properties"})的注解,对于@ConfigurationProperties注解不需要修改,@PropertiesSource用于指定properties文件的位置,让Spring Boot在启动的时候进行加载,这里的value支持数组,也就是可以一次加载多个配置文件,另外,这里classpath:后面不要带空格,多带了空格会提示找不到文件,我就犯了这样一个错误。
@ImportResource:导入Spring的配置文件,让配置文件里的内容生效
在Spring Boot里面是没有Spring的配置文件的,即使我们编写了配置文件,Spring Boot也不会自动识别。要想让Spring的配置文件生效,就需要将@ImportResource注解标注在一个配置类上,才能让Spring配置文件生效。
我们在resources文件夹中创建一个beans.xml文件,作为Spring的配置文件。根据配置文件中helloService的class属性,在包中创建出对应的类。同时,编写一个测试类,观察helloService能否通过Spring容器拿到。
<?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.atguigu.springboot.service.HelloService"/>
</beans>
package com.atguigu.springboot.service;
public class HelloService {
}
package com.atguigu.springboot;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
@SpringBootTest
class SpringBoot02ConfigApplicationTests {
@Autowired
ApplicationContext applicationContext;
@Test
void testHelloService() {
boolean exist = applicationContext.containsBean("helloService");
System.out.println(exist);
}
}
经测试,并不能通过applicationContext中获取到helloService。于是,就需要通知Spring Boot把Spring的配置文件进行导入,我们在主启动类上,加上@ImportResource(locations = {"classpath:beans.xml"})即可。此时再运行测试类,发现可以通过applicationContext取到helloService了,说明Spring的配置文件被Spring Boot导入了。
@Bean:给容器中加组件
Spring Boot中不建议写xml,因此推荐使用注解的方式给容器中加入组件。创建MyAppConfig.java,并将主启动类上的@ImportResource注解去掉,再次运行测试类,同样可以从applicationContext中获取到HelloService的实例。
package com.atguigu.springboot.config;
import com.atguigu.springboot.service.HelloService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// @Configuration:告诉Spring Boot这是一个配置类,替代之前Spring的配置文件
@Configuration
public class MyAppConfig {
// @Bean:将方法的返回值添加到容器中,这里的@Bean类似于<bean>标签,容器中这个组件默认id是方法名
@Bean
public HelloService helloService() {
System.out.println("通过配置类给容器中添加组件");
return new HelloService();
}
}
4.配置文件占位符
在properties文件和yml文件中,可以使用占位符来表示一些值,需要在value的部分带上${key}即可。
1.随机数
${random.value}、${random.int}、${random.long}、${random.int(10)}、${random.int(1024,65536)}
2.占位符获取前面的配置值,如果没有,可以使用:指定默认值
person.name=张三
person.age=${age:10}
person.dog.name=${person.name}的狗
5.Profile
Profile是Spring对不同环境提供不同配置功能的支持,可以通过激活、指定参数等方式快速切换环境。
1.多Profile文件
在写配置文件的时候,创建多个properties或者yml文件,文件名以application-{profile}.properties/yml格式,默认只会读取application.properties配置文件。
2.yml支持多文档块方式
通过控制spring.profile.active的值来切换环境。
在yml里,---表示文档块的分隔符。
spring:
profiles:
active: test
---
server:
port: 8080
spring:
profiles: dev
---
server:
port: 8081
spring:
profiles: prod
---
server:
port: 8082
spring:
profiles: test
3.激活指定的Profile
- 在默认配置文件中,指定spring.profile.active=dev即可激活application-dev.properties配置文件。
- 命令行激活,打开启动设置,在Program arguments中添加--spring.profiles.active=dev,表示以dev环境运行。或者在执行jar包的时候,带上参数,比如java -jar a.jar --spring.profiles.active=dev
- 虚拟机参数,打开启动设置,在VM options中添加-Dspring.profiles.active=dev,这里的-D是固定写法。
6.配置文件加载位置
Spring Boot启动后会扫描以下位置的配置文件,从上到下优先级依次递减,作为Spring Boot的默认配置文件。
- -file:./config/
- -file:./
- -classpath:/config/
- -classpath:/
Spring Boot会从这4个位置读取全部的配置文件,以互补配置原则进行配置。还可以通过配置spring.config.location来改变配置文件读取的位置。需要将项目打包成jar包,以java -jar方式启动的时候,带上--spring.config.location=×××的形式来指定配置文件的位置,此时指定的配置文件和项目中原有的配置文件以互补的形式共同起作用。这里指定的位置可以任意,即使不在项目路径下也是可以的。
7.外部配置加载顺序
Spring Boot可以从外部位置加载配置文件,按照以下规则,从上到下优先级依次递减,同样满足高优先级覆盖低优先级,所有配置文件互补原则。
1.命令行参数
所有的配置都可以在命令行上进行指定,多个配置使用空格分开,参数使用--配置项=值的形式书写。
java -jar a.jar --server.port=8081 --server.servlet.context-path=/hello
2.来自java:comp/env的JNDI属性
3.Java系统属性(System.getProperties())
4.操作系统环境变量
5.RandomValuePropertySource配置random.*属性值
6.jar包外部的application-{profile}.properties或application.yml(带spring.profile)配置文件
7.jar包内部的application-{profile}.properties或application.yml(带spring.profile)配置文件
8.jar包外部的application.properties或application.yml(不带spring.profile)配置文件
9.jar包内部的application.properties或application.yml(不带spring.profile)配置文件
10.@Configuration注解类上的@PropertySource
11.通过SpringApplication.setDefaultProperties指定的默认属性
其实,只需要掌握常用的即可,其中1,6,7,8,9是常用的,其他的仅做了解即可。另外Spring Boot官方支持17种外部配置方式:https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/htmlsingle/#boot-features-external-config,了解即可。
8.自动配置原理
关于配置文件的属性值,默认值,描述等信息,参考Spring Boot的官方文档:https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/htmlsingle/#common-application-properties。
1.自动配置原理:
1.Spring Boot启动的时候,加载主配置类,通过注解@EnableAutoConfiguration开启了自动配置功能
2.@EnableAutoConfiguration的作用
- 利用EnableAutoConfigurationImportSelector给容器导入一些组件
- 依次点击@SpringBootApplication、@EnableAutoConfiguration、AutoConfigurationImportSelector.class。依次找到selectImports()方法、getAutoConfigurationEntry()方法、getCandidateConfigurations()方法、loadFactoryNames()方法、loadSpringFactories()方法,查看方法体里,有写一个META-INF/spring.factories,找到类路径下jar包中对应的目录(比如spring-boot-autoconfigure的jar包),会看到org.springframework.boot.autoconfigure.EnableAutoConfiguration=\这一行,这一行下面的类名,就是使用@EnableAutoConfiguration注解后,会被自动注入Spring容器的类。每一个×××AutoConfiguration类都是容器中的一个组件,加入到容器中用作自动配置。
3.每一个类按照类上的注解进行相应的自动配置功能
4.以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理
找到HttpEncodingAutoConfiguration类,它的类名上有一些注解,拿出来一一说明。
// @Configuration表明HttpEncodingAutoConfiguration类是一个配置类,通过使用proxyBeanMethods=false可以减少启动时间和内存使用量
@Configuration(proxyBeanMethods = false)
// 启用EnableConfigurationProperties功能,将配置文件中的值和HttpProperties进行绑定,并把HttpProperties加入到容器中
// 这里可以点进HttpProperties类看一下,类名上面有一个@ConfigurationProperties(prefix = "spring.http")的注解,代表会读取配置文件中以“spring.http”开头的配置和bean进行绑定
@EnableConfigurationProperties({HttpProperties.class})
// Spring底层的@Conditional注解,根据是否符合条件,决定是否启用配置,这里的OnWebApplication意思是判断当前应用是不是Web应用,如果是,当前配置生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 判断当前目录有没有CharacterEncodingFilter类,这个类是SpringMVC中的编码过滤器
@ConditionalOnClass({CharacterEncodingFilter.class})
// 判断配置文件中是否存在spring.http.encoding.enable,如果不存在表示spring.http.encoding.enable=true,相当于做了一个默认设置
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
查看characterEncodingFilter()方法,用于给容器中添加一个Bean,在方法体内可以看到,会从properties中读取值set进去。
5.所有能在配置文件中配置的属性都是在×××Properties类中封装着,通过@ConfigurationProperties的prefix的值以及类中的属性名,确定在配置文件中写什么内容,从而完成绑定功能
Spring Boot的精髓:
- Spring Boot在启动的时候,会加载大量的自动配置类
- 查看我们需要的功能,Spring Boot是否有默认的自动配置类
- 查看自动配置类中配置了哪些组件,如果有我们需要用的组件,就不需要再来配置了
- 给容器中自动配置类添加组件时,会从×××Properties类中获取到属性值,于是我们需要在配置文件中指定这些值
2.细节
在自动配置类上面,通常会有@Conditional注解,表示在什么场景下,对这个类进行自动配置。
1.@Conditional派生注解
只有@Conditional指定的条件成立,才会给容器中添加组件,配置项里面的内容才会生效。
@Conditional扩展注解 | 作用(判断是否满足当前指定的条件) |
@ConditionalOnJava | 系统的Java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean |
@ConditionalOnMissingBean | 容器中不存在指定Bean |
@ConditionalOnExpression | 满足Spring Expression Language |
@ConditionalOnClass | 系统中有会指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCadidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定属性是否有指定值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是Web环境 |
@ConditionalOnNotWebApplication | 当前不是Web环境 |
@ConditionalOnJndi | JDNI存在指定项 |
我们可以在配置文件中加入debug=true属性开启Spring Boot的debug模式,启动主配置类,通过控制台查看哪些配置类生效,哪些配置类没有生效。
其中Positive matches表示自动配置类启用的,Negative matches表示自动配置类没有启用的。