Spring Boot 配置文件
🔍 配置文件
1. 配置文件
SpringBoot 使用一个全局的配置文件,配置文件名是固定的:
-
application
.properties
语法结构 :key = value
-
application
.yml
语法结构 :key:空格 value
📜 标记语言:
以前的配置文件;大多都使用的是 xxxx.xml 文件;
YAML:以数据为中心,比 json、xml 等更适合做配置文件;
💬 比如我们可以在配置文件中修改 Tomcat 默认启动的端口号
YAML:
server:
port: 8081
properties:
server.port=8081
2. YAML 语法
① 基本语法
k: v
:表示一对键值对(空格必须有)
以空格的缩进来控制层级关系,只要是左对齐的一列数据,都是同一个层级的
server:
port: 8081
path: /hello
属性和值的大小写都是十分敏感的。
② 值的写法
Ⅰ 字面量:普通的值(数字,字符串,布尔)
字面量直接写在后面就可以 , 字符串默认不用加上双引号或者单引号;
k: v
-
" "
:双引号;不会转义字符串里面的特殊字符;特殊字符会作为本身想表示的意思💬 例如:
name: "zhangsan \n lisi"
输出:
zhangsan lisi
-
' '
:单引号;会转义特殊字符,特殊字符最终只是一个普通的字符串数据💬 例如:
name: ‘zhangsan \n lisi’
输出
zhangsan \n lisi
Ⅱ Map(属性和值)(键值对)
k: v
:在下一行来写对象的属性和值的关系,注意缩进
#对象、Map格式
k:
v1:
v2:
💬 例如:
friends:
lastName: zhangsan
age: 20
行内写法:
friends: {lastName: zhangsan,age: 18}
Ⅲ 数组(List、Set)
用 - 值
表示数组中的一个元素
💬 例如:
pets:
- cat
- dog
- pig
行内写法
pets: [cat,dog,pig]
3. @ConfigurationProperties 全局配置文件值注入
① 全局配置文件值注入
📜 需要导入配置文件处理器的依赖,配置文件进行绑定就会有提示
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
注入步骤:
- 在 springboot 项目中的 resources 目录下新建一个文件
application.yml
- 编写实体类
配置文件 yaml
格式
person:
lastName: hello
age: 18
boss: false
birth: 2017/12/12
maps: {k1: v1,k2: 12}
lists:
- lisi
- zhaoliu
dog:
name: 小狗
age: 12
配置文件 properties
格式
person.last-name=张三
person.age=11
person.birth=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=15
🚨 properties 配置文件在 IDEA 中默认 utf-8 可能会乱码,如图进行相应设置:
实体类:
/**
* 将配置文件中配置的每一个属性的值,映射到这个组件中
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
// 有参无参构造、get、set方法、toString()方法
-
@ConfigurationProperties
:告诉 SpringBoot 将本类中的所有属性和配置文件中相关的配置进行绑定;⭐ 只有这个组件是容器中的组件,才能使用容器提供的
@ConfigurationProperties
功能; -
prefix = "person"
:与配置文件中哪个属性进行一一映射
② @Validated(JSR303 数据校验)
Ⅰ 概述
JSR303数据校验 , 就是我们可以在字段上增加一层过滤器验证 , 保证数据的合法性
使用 @Validated
注解:表示这个 JavaBean 中的属性需要校验
@Component
@ConfigurationProperties(prefix = "person")
@Validated
public class Person {
//lastName必须是邮箱格式
@Email
private String lastName;
private Integer age;
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
Ⅱ 常见参数
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
// 空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
// Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
// 长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
// 日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
4. @Value 值注入
@Value
值注入是 Spring 中的方式
① @Value 值注入
可以使用 @Value
注解从配置文件中手动取值或者自由赋值,支持三种方式:
-
${key}
从环境变量、配置文件中获取值@Value("${person.last-name}") private String lastName;
-
# {SpEL}
@Value("#{11*2}") private Integer age;
-
"字面量"
@Value("true") private Boolean boss;
@Component
public class Person {
/**
* <bean class="Person">
* <property name="lastName" value="字面量//"></property>
* <bean/>
*/
@Value("${person.last-name}")
private String lastName;
@Value("#{11*2}")
private Integer age;
@Value("true")
private Boolean boss;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
② @Value 和 @ConfigurationProperties 注入值比较
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
💡 松散绑定:比如 yml 中写的
last-name
,这个和lastName
是一样的,-
后面跟着的字母默认是大写的。这就是松散绑定。
无论配置文件是 yml
还是 properties
他们都能获取到值;
-
如果只是在某个业务逻辑中需要获取一下配置文件中的某项值,使用
@Value
;@RestController public calss HelloController{ @Value("${person.lastName}") private String name; @RequestMapping("/sayHello") public String sayHello(){ return "Hello" + name; } }
-
如果我们专门编写了一个 JavaBean 来和配置文件进行映射,就直接使用
@ConfigurationProperties
5. @PropertySource 指定配置文件值注入
@ConfigurationProperties(prefix = "person")
默认从全局配置文件中获取值,如果所有的配置全写在全局配置文件中,那么这个文件就太大了。我们可以自定义配置文件使用 @PropertySource
加载指定的配置文件
比如我们在 resources 目录下新建一个 person.properties
文件
@PropertySource(value = {"classpath:person.properties"})
@Component
public class Person {
private String lastName;
private Integer age;
private Boolean boss;
6. @ImportResource / @Bean
@ImportResource
:导入 Spring 的配置文件,让配置文件里面的内容生效
(Spring Boot 里面没有 Spring 的配置文件,我们自己编写的配置文件,也不能自动识别)
@ImportResource
标注在一个配置类上可以让 Spring 的配置文件生效并加载进来;
@ImportResource(locations = {"classpath:beans.xml"})
@SpringBootApplication
Spring 的配置文件 beans.xml
(示例:我们创建一个 service.HelloService.java 文件)
<?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.smallbeef.springboot.service.HelloService"></bean>
</beans>
🌎 显然,这样给容器中添加组件的方法比较麻烦,SpringBoot 推荐给容器中添加组件的方式:使用全注解的方式:
-
创建一个 config.MyAppConfig.java 配置类
-
⭐ 使用@Bean 给容器中添加组件
@Configuration
表示此类是 Spring 的配置类(替代原来的 Spring 配置文件)/** * @Configuration:指明当前类是一个配置类;就是来替代之前的Spring配置文件 */ @Configuration public class MyAppConfig { //将方法的返回值添加到容器中;容器中这个组件默认的 id 就是方法名 @Bean public HelloService helloService02(){ System.out.println("配置类 @Bean 给容器中添加组件了..."); return new HelloService(); } }
7. 配置文件占位符
① 随机数
配置文件还可以编写占位符生成随机数
${random.value}
${random.int}
${random.long}
${random.int(10)}
${random.int[1024,65536]}
② 占位符获取之前配置的值
person.last-name = 张三${random.uuid}
person.age = ${random.int}
# 在 person.last-name 后面 添加 _dog 字符
person.dog.name=${person.last-name}_dog
如果没有可以是用 :
指定默认值
person.last-name=张三${random.uuid}
# person.dog.name = hello_dog
person.dog.name=${person.hello:hello}_dog
8. Profile 多环境切换
profile 是 Spring 对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境 👇
① 多配置文件
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties/yml
比如:application-dev.properties
、application-prod.properties
默认使用 application.properties
的配置
② 激活指定环境
-
⭐ 在配置文件中指定
spring.profiles.active=dev
-
命令行:
java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;
可以直接在测试的时候,配置传入命令行参数
-
虚拟机参数;
-Dspring.profiles.active=dev
③ yml 支持多文档块方式
和 properties
配置文件中一样,但是使用 yml
去实现不需要创建多个配置文件,更加方便了~
server:
port: 8081 # 默认端口
#选择要激活那个环境块
spring:
profiles:
# 激活
active: prod # 使用 prod 环境,8084端口
---
server:
port: 8083
spring:
profiles: dev # 配置环境的名称
---
server:
port: 8084
spring:
profiles: prod # 配置环境的名称
9. 配置文件加载位置
springboot 启动会扫描以下位置的 application.properties
或者 application.yml
文件作为 Spring boot 的默认配置文件
-
file:./config/
项目路径下的 config 文件夹配置文件 -
file:./
项目路径下配置文件 -
classpath:/config/
资源路径下的 config 文件夹配置文件 -
classpath:/
资源路径下配置文件
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot 会从这四个位置全部加载主配置文件,⭐ 互补配置;
📜 我们还可以通过
spring.config.location
来改变默认的配置文件位置项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;指定配置文件和默认加载的这些配置文件共同起作用形成互补配置;
比如我们项目打包后后我们指定配置文件的新位置为 E:/application.properties
java -jar spring-boot-02-config-02-0.0.1-SNAPSHOT.jar --spring.config.location=E:/application.properties
10. 自动配置原理
配置文件到底能写什么?怎么写?配置文件能配置的属性参照
① 自动配置原理 ⭐
Ⅰ 加载主配置类,开启自动配置功能
⭐ @SpringBootApplication
(Spring Boot应用):标注在某个类上说明这个类是 SpringBoot 的主程序类,SpringBoot 就应该运行这个类的 main
方法来启动 SpringBoot 应用;
📑 该注解源码如下:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
底层由三个注解实现:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
⭐ 自动配置第 1 步:SpringBoot 启动的时候加载主配置类(由
@SpringBootConfiguration
标识),并开启了自动配置功能@EnableAutoConfiguration
Ⅱ 导入组件(自动配置类)
@EnableAutoConfiguration
:开启自动配置功能。
以前我们需要配置的东西,Spring Boot 帮我们自动配置;
@EnableAutoConfiguration
告诉 SpringBoot 开启自动配置功能,这样自动配置才能生效;
📑 该注解的源码如下:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
-
⭐
@AutoConfigurationPackage
:自动配置包,将主配置类(@SpringBootApplication
标注的类)的所在包及下面所有子包里面的所有组件扫描到 Spring 容器中📑 该注解的源码如下:
@Import({Registrar.class}) public @interface AutoConfigurationPackage { }
-
@Import({Registrar.class})
:@import
是 Spring 中的底层注解,给容器中导入一个组件 -
Registrar.class
作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到 Spring 容器 ;
-
-
⭐
@Import({AutoConfigurationImportSelector.class})
:给容器中导入组件。AutoConfigurationImportSelector
:自动配置导入选择器。将所有需要导入的组件以全类名的方式返回;这些组件就会被添加到容器中。说白了就是导入组件那么它会导入哪些组件的选择器呢?📑 我们点击去这个类
AutoConfigurationImportSelector
看源码:这个类中有一个这样的方法:
// 获得候选的配置 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { // 这里的 getSpringFactoriesLoaderFactoryClass() 方法 // 返回的就是启动自动导入配置文件的注解类;EnableAutoConfiguration List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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; }
这个方法又调用了
SpringFactoriesLoader
类的静态方法,我们进入SpringFactoriesLoader
类loadFactoryNames()
方法:public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); // 这里它又调用了 loadSpringFactories 方法 return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList()); }
我们继续点击查看
loadSpringFactories
方法:private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) { // 获得classLoader , 我们返回可以看到这里得到的就是EnableAutoConfiguration标注的类本身 MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); if (result != null) { return result; } else { try { //去获取一个资源 "META-INF/spring.factories" Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories"); LinkedMultiValueMap result = new LinkedMultiValueMap(); //将读取到的资源遍历,封装成为一个Properties while(urls.hasMoreElements()) { URL url = (URL)urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); Iterator var6 = properties.entrySet().iterator(); while(var6.hasNext()) { Entry<?, ?> entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String factoryName = var9[var11]; result.add(factoryClassName, factoryName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException var13) { throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); } } }
⭐ 在上面这段源码中,我们发现了一个多次出现的文件
spring.factories
,在 External Libraries 中找到它:其中包含了很多自动配置的文件(自动配置类
xxxAutoConfiguration
),这就是自动配置的根源所在!我们在上面的自动配置类随便找一个打开看看,比如 :
WebMvcAutoConfiguration
可以看到这些一个个的都是JavaConfig 配置类,而且都注入了一些 Bean。
⭐ 自动配置第 2 步:利用
AutoConfigurationImportSelector
给容器中导入一些组件:-
扫描所有 jar 包类路径下
META-INF/spring.factories
-
把扫描到的这些文件的内容包装成
properties
对象 -
从
properties
中获取到EnableAutoConfiguration.class
类(类名)对应的值,然后把他们添加在容器中
总结来说,就是利用
SpringFactoriesLoader.loadFactoryNames()
将类路径下META-INF/spring.factories
里面配置的所有EnableAutoConfiguration
的值加入到了容器中。每一个这样的xxxAutoConfiguration
自动配置类都是容器中的一个组件,都加入到容器中,用他们来做自动配置; -
Ⅲ 举例分析自动配置原理
📑 以 HttpEncodingAutoConfiguration
(Http 编码自动配置)为例解释自动配置原理:
// 表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@Configuration
// 启动指定类的ConfigurationProperties功能;
// 将配置文件中对应的值和 HttpEncodingProperties 绑定起来;
// 并把HttpEncodingProperties加入到ioc容器中
@EnableConfigurationProperties(HttpEncodingProperties.class)
// Spring底层 @Conditional 注解,根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效;
// @ConditionalOnWebApplication 判断当前应用是否是 web 应用,如果是,当前配置类生效
@ConditionalOnWebApplication
// 判断当前项目有没有这个类 CharacterEncodingFilter:SpringMVC 中进行乱码解决的过滤器;
@ConditionalOnClass(CharacterEncodingFilter.class)
// 判断配置文件中是否存在某个配置 spring.http.encoding.enabled;如果不存在,判断也是成立的
// 即使我们配置文件中不配置 spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他已经和 SpringBoot 的配置文件映射了
private final HttpEncodingProperties properties;
// 只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从 properties 中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) // 判断容器有没有这个组件
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;
}
🚩 自动配置类:根据当前不同的条件判断,决定这个配置类是否生效。
-
一但这个配置类生效;这个配置类就会给容器中添加各种组件;
-
这些组件的属性是从对应的
properties
类中获取的,这些类里面的每一个属性又是和配置文件绑定的; -
所有在配置文件中能配置的属性都是在
xxxxProperties
类中封装着,配置文件能配置什么就可以参照某个功能对应的这个属性类📑 比如
HttpEncodingProperties
://从配置文件中获取指定的值和 bean 的属性进行绑定 @ConfigurationProperties(prefix = "spring.http.encoding") public class HttpEncodingProperties { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
我们去配置文件里面试试前缀,看看提示:
⭐ 精髓:
-
SpringBoot 启动会加载大量的自动配置类
-
我们看我们需要的功能有没有 SpringBoot 默认写好的自动配置类;
xxxxAutoConfigurartion
:自动配置类,给容器中添加组件xxxxProperties
:封装配置文件中相关属性 -
我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件有,我们就不需要再来配置了)
-
给容器中自动配置类添加组件的时候,会从
properties
类中获取某些属性。我们就可以在配置文件中指定这些属性的值;
② @Conditional 派生注解
作用:必须是 @Conditional
指定的条件成立,才给容器中添加组件,配置文件里面的所有内容才生效;
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
❓ 我们怎么知道哪些自动配置类生效?
我们可以通过在配置文件启用 debug = true
属性,来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。
#开启springboot的调试类
debug = true
控制台打印输出示例如下:
=========================
AUTO-CONFIGURATION REPORT
=========================
Positive matches:(自动配置类启用的)
-----------------
DispatcherServletAutoConfiguration matched:
- @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
- @ConditionalOnWebApplication (required) found StandardServletEnvironment (OnWebApplicationCondition)
Negative matches:(没有启动,没有匹配成功的自动配置类)
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'javax.jms.ConnectionFactory', 'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)
AopAutoConfiguration:
Did not match:
- @ConditionalOnClass did not find required classes 'org.aspectj.lang.annotation.Aspect', 'org.aspectj.lang.reflect.Advice' (OnClassCondition)
-
Positive matches:(自动配置类启用的:正匹配)
-
Negative matches:(没有启动,没有匹配成功的自动配置类:负匹配)
-
Unconditional classes: (没有条件的类)