🔍 配置文件
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: (没有条件的类)