关于 SpringBoot 的配置的相关详细信息,可以参考官方文档。
1. 配置文件
创建 SpringBoot 项目时会默认创建一个 全局配置文件 ,配置文件名是固定的,即 application.properties。另外,我们也可以创建另一种格式的全局配置文件,即 application.yml。它与 application.properties 相同,只是书写规范不一样。
全局配置文件的作用是修改 SpringBoot 自动配置的默认值
,SpringBoot 在底层都给我们自动配置好。关于配置文件能配置的属性可参照附录 A 。
2. YAML
前面我们讲过 SpringBoot 的默认配置文件可以是 application.yml,其中 .yml 是 YAML(YAML Ain’t Markup Language)语言的文件,YAML 以数据为中心,比 json、xml 等更适合做配置文件。我们可以在 YAML 的官方文档参考其语法规范。当然, SpringBoot 也有关于如何使用 YAML 的使用文档,里面有具体的说明。下面我们来了解一下 YAML 的一些简单语法吧。
2.1 YAML 语法
2.1.1 IDEA 环境
首先为了我们更为方便正确地操作 application.yml 来使用 YAML 语法,我们可以调用 IDEA 的提示功能,首先我们需要在项目的 pom 文件导入下面的依赖,重新运行项目即可生效。
1 | <dependency> |
2.1.2 基本语法
下面是 YAML 的一个实例,其用于更改 SpringBoot 应用的启动端口号:
1 | server: |
YAML 的基本语法遵循如下几个约定:
- 使用缩进表示层级关系;
- 缩进时不允许使用 Tab 键,只允许使用空格;
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可;
- 大小写敏感;
- key 和 value 之间必须使用空格分隔。
2.1.3 数据结构
YAML 支持的三种数据结构,分别为对象、数组和字面量。下面我们分别来了解一下 YAML 如何对这些数据的支持。
字面量:即普通的值(数字,字符串,布尔,日期),示例如下:
1 | person: |
这里需要注意的是字符串默认不用加上单引号或者双引号的,当加上引号后,它们符合不一样的规则,如下:
- “”:双引号,不会转义字符串里面的特殊字符,特殊字符会作为本身想表示的意思输出,比如:
输入:name: "zhangsan\nlisi"
,
输出:
1 | zhangsan |
- ‘’:单引号,会转义特殊字符,特殊字符最终只是一个普通的字符串数据。
输入:name: ‘zhangsan \n lisi’
输出;zhangsan \n lisi
对象:即普通的对象,Map
我们平常写的普通对象和 Map 数据结构,YAML 都是一个 key: value 的形式表示,使用冒号分隔,冒号后需要带上空格。如:username: admin。示例如下:
1 | # 普通对象 person |
以上的示例是块内写法,其也支持行内写法,示例如下:
1 | object: {k1: v1, k2: v2} |
数组:普通数组,List,Set
块内写法:
1 | list: |
行内写法:
1 | list: [abc, efg jhg] |
3. 配置文件值注入
SpringBoot 提供了一个功能,我们可以将全局配置文件与具体的 Bean 进行绑定,并将配置文件中配置的值注入到指定的 Bean 中。下面我们来了解一下这个功能的使用。
3.1 @ConfigurationProperties
比如,我们在 application.yml 进行了如下配置,我们可以视 person 和 dog 为两个普通对象的别名:
1 | person: |
当然,除了 application.yml 之外,我们也可以在 application.properties 进行如下配置,其和 application.yml 中的配置作用一样的,只是不是 YAML 格式的。
1 | # 配置 Person 类 |
另外,需要说明的是,在 IDEA 中需要设置 application.properties 文件的编码,如下图所示:
紧接着我们同时定义了如下两个类 ,分别为 Person 和 Dog:
1 |
|
1 |
|
这里我们使用了 Lombok,Lombok 的使用请自行在网上查找,相关注解的作用已经在注释给了说明。一般情况下,Java Bean 的构造方法(有参和无参)、 getter 以及 setter 方法都是不可忽略的,而 Lombok 可以简化代码的编写。
观察上述的 Person 类,我们使用到了 @ConfigurationProperties(prefix = "person")
注解,这个注解用于将配置文件(默认是全局配置文件)的每一个属性的值,映射并绑定到当前的 Person 类中,从而实现属性的注入。其中 prefix = “person” 表示与全局配置文件中的 person 进行绑定,切忌出错,否则将无法映射成功。
另外,需要注意的是,我们在使用 @ConfigurationProperties 注解时,同时也要将该类加入到 IoC 容器中,故这里也使用了 @Component 注解。
最后,我们使用单元测试来验证是否实现配置文件值得注入,测试的代码如下所示:
1 |
|
运行后输出如下结果,说明配置文件值得注入成功:
1 | Person(lastName=张三, age=18, boss=false, birth=Wed Nov 27 00:00:00 CST 2019, list=[a, b, c, d], map={k1=v1, k2=v2}, dog=Dog(name=旺财, age=3), arr=[1, 2, 3]) |
3.1.1 属性名匹配规则(松散绑定)
前面我们在 application.properties 进行了如下配置,这里我们注意 person.last-name 的编写规则:
1 | # 配置 Person 类 |
我们在 Person 类中定义了属性 lastName,其是符合驼峰命名规则的,因此在 application.properties 中我们可以有如下几种表示方法,它们的作用是相同的,即是一种松散绑定的规则。
1 | person.last-name=张三 |
3.2 @Value
除了 @ConfigurationProperties 注解能够实现 JavaBean 值的注入之外,@Value 注解同样也是可以实现的。
首先我们定义如下的 JavaBean,其中注解 @Value 遵循 ${key},普通字面量和 #{SpEL}
三种格式,具体使用参考如下代码:
1 |
|
然后在application.properties 中编写如下代码,这样 @Value("${user.last-name}")
便可以从中获取值。
1 | # 配置 User 类 |
单元测试输出 User 对象后,我们可以发现是能够成功的进行属性的注入的。
3.2.1 与@ConfigurationProperties 的比较
@ConfigurationProperties | @Value | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定(松散语法) | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
观察上表,如果我们只是在某个业务逻辑中需要获取一下配置文件中的某项值,推荐使用 @Value;如果我们专门编写了一个 JavaBean 来和配置文件进行映射,我们就直接使用 @ConfigurationProperties。
@Value 是不支持松散绑定的,比如我们在 application.properties 编写了 user.last-name
,那么我们使用 @Value(${user.lastName})
或者使用 @Value(${user.last_name})
是不支持的。
@Value 同样不支持复杂类型的封装,比如向 Map 数据类型的属性注入值等等。对于 @ConfigurationProperties 与 @Value 在 JSR303 数据校验方面的使用和其区别,下面我们对其进行了解。
3.3 @Validated
使用上述 @Value 的代码示例,我们在类前加上注解 @Validated
来开启数据校验的功能,然后在属性 lastName 上加上注解 @Email
,表明该属性必须符合邮箱的格式。单元测试后,我们是可以正常输出 User 对象的,那么就说明数据校验未成功(lastName 的值李四
并不符合邮箱格式,但是还是正常输出了),因此@Value
并不支持 JSR303 数据校验功能。
下面我们更改代码如下,我们使用注解 @ConfigurationProperties 来验证数据校验功能:
1 |
|
全局配置文件的配置如下:
1 | # 配置 User 类 |
单元测试输出 User 对象时,会报如下错误。因此注解 @ConfigurationProperties 是支持数据校验功能的。
1 | Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'user' to com.shoto.bean.User failed: |
3.4 @PropertySource
由于注解 @ConfigurationProperties 是默认从全局配置文件中获取值的,为了避免全局配置文件过于臃肿,我们可以另外创建一个配置文件,然后配合使用 @PropertySource 注解来获取该配置文件的内容。
现在我们定义如下的一个 JavaBean,具体代码如下所示:
1 |
|
注解 @PropertySource(value = {“classpath:cat.properties”}) 表明我们在类路径下创建了一个配置文件 cat.properties,其内容如下所示:
1 | cat.name=cat |
单元测试输出 Cat 对象可以发现对象是成功注入属性值的。
3.5 @ImportResource
该注解可以导入外部的配置文件,比如导入 Spring 的配置文件。由于 SpringBoot 本身是没有 Spring 的配置文件的,我们自己编写的配置文件,也不能自动识别。为了让 Spring 的配置文件生效,我们可以将 @ImportResource 标注在一个配置类上。
首先我们编写如下一个配置类,注解 @Configuration 表明该类是一个配置类,@ImportResource 指明 Spring 配置文件的位置。具体内容如下所示:
1 | "classpath:beans.xml"}) (locations = { |
接着在类路径下编写 Spring 的配置文件 beans.xml,用于将 IndexController 类作为组件加入到 IoC 容器中,配置文件内容如下:
1 |
|
然后,编写如下单元测试代码,判断 IndexController 类是否成功加入到 IoC 容器中:
1 |
|
当然,SpringBoot 推荐以全注解的方式给容器中添加组件,利用 @Bean 注解我们可以免去 beans.xml 配置文件,编写如下配置类即可实现:
1 |
|
4. 配置文件占位符
4.1 随机数
SpringBoot 的配置文件支持使用随机数的相关命令,参考如下:
1 | ${random.value}、${random.int}、${random.long} |
比如我们在全局配置文件中进行如下配置,当将配置文件的值注入到 Bean RandomNumber 中并打印对象后,可能输出如下结果。
1 | # 随机数演示 |
1 | RandomNumber(secret=cbe7ca3fdd7583e6f71313fe1f2889ed, number=-1697810257, bigNumber=6284689835040742963, uuid=4b166086-4263-4dcf-b102-6b97cdd889e0, numberLessThanTen=7, numberInRange=18200) |
4.2 配置占位符
占位符的值除了可以产生随机数之外,还可以引用前面配置过的属性,比如以下示例:
1 | app.name=MyApp |
除此之外,假设没有引用到前面配置过得属性,这时我们还可以给他一个默认值,比如给 app.name 默认设为 chatApp:
1 | app.description=${app.name:chatApp} is a String Boot application |
5. Profile
Profile 是 Spring 对不同环境提供不同配置功能
的支持,可以通过激活、指定参数等方式快速切换环境,即可以根据不同的环境快速的切换相应的配置文件(文档块)。
5.1 多 Profile 文件方式
比如现在有默认、开发和生产三种环境,它们需要不同的配置文件,分别为 application.properties(默认文件)、application-dev.properties 以及 application-prod.properties。文件的命名格式遵循 application-{profile}.properties/yml 这种格式。下面是上述三个个文件的具体内容:
1 | # application.properties |
1 | # application-dev.properties |
1 | # application-peod.properties |
启动 SpringBoot 应用后,我们会发现 application-dev.properties 配置文件中的配置会生效,即启动端口变为 8081。
5.2 多文档块方式(yml文件)
除了可以使用多 profile 文件的形式之外,对于 .yml 格式的配置文件,我们还可以以多文档块(以符号—来分隔)的形式来实现相同的功能,具体如下:
1 | server: |
5.3 激活方式
前面我们已经在配置文件中使用 spring:profiles:active
来激活切换其他环境,除此之外,我们还有另外两种方式:
命令行:我们可以在命令行工具输入如下命令,通过传入命令行参数来激活指定的环境。
1
java -jar springboot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
虚拟机参数:
1
-Dspring.profiles.active=dev
6. 配置文件加载位置
SpringBoot 启动会扫描以下位置的 application.properties 或者 application.yml 文件作为 SpringBoot 的默认全局配置文件:
- file:./config/
- file:./
- classpath:/config/
- classpath:/
以上是按照优先级从高到低
的顺序,所有位置的文件都会被加载
,高优先级配置内容会覆盖低优先级的相同的
配置内容,也就是互补配置。
另外,我们还可以通过 spring.config.location 来改变默认的配置文件位置。比如项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置。命令行参数指定的配置文件和项目中的这些配置文件共同起作用形成互补配置。示例如下:
1 | java -jar springboot-02-config-0.0.1-SNAPSHOT.jar --spring.config.location=G:/application.properties |
7. 外部配置加载顺序
SpringBoot 也可以从以下位置加载配置, 优先级从高到低,高优先级的配置覆盖低优先级的配置,所有的配置会形成互补配置。
命令行参数
1
2java -jar springboot-02-config-02-0.0.1-SNAPSHOT.jar --server.port=8087 --server.s
ervlet.context-path=/index来自java:comp/env的JNDI属性
Java系统属性(System.getProperties())
操作系统环境变量
RandomValuePropertySource配置的random.*属性值
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指定的默认属性
SpringBoot 还支持其他外部化配置,所有的外部化配置加载顺序可访问官方文档。
8. 自动配置原理
8.1 配置原理详情
1) 、SpringBoot 启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration:
2)、@EnableAutoConfiguration 作用:
首先我们点击 @EnableAutoConfiguration 的子注解 @Import({AutoConfigurationImportSelector.class}) ,在类 AutoConfigurationImportSelector 中我们可以看到 getCandidateConfigurations 方法:
点击 loadFactoryNames 方法后点击其调用的 loadSpringFactories 方法,该方法的内容如下所示:
该方法会扫描所有jar包类路径下 META-INF/spring.factories 的文件,并将文件的内容包装成 Properties 对象,并从中获取到xxxAutoConfiguration 类(类名),然后把他们添加在容器中。也就是会将类路径下 META-INF/spring.factories 里面配置的所有xxxAutoConfiguration 的值加入到了容器中:
1 | # Auto Configure |
每一个这样的 xxxAutoConfiguration 类都是容器中的一个组件,都加入到容器中,从而实现自动配置的功能。
3)、以 HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理;
1 | //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件 |
4)、所有在配置文件中能配置的属性都是与其对应的封装类 xxxxProperties(比如上面的 HttpEncodingProperties 类) 一一对应的,因此配置文件能配置什么就可以参照某个功能对应的这个属性类。我们可以从类 HttpEncodingProperties 中知道它是与配置文件中的 spring.http 进行绑定的。
1 | ( |
自动配置原理的总结如下:
- SpringBoot 的 @EnableAutoConfiguration 用于开启自动配置功能;
- 然后 SpringBoot 会扫描 spring.factories 文件中的所有自动配置类,并给容器中添加相关的组件;
- 其中每个自动配置类都与配置文件中的属性进行绑定,而这些配置文件的体现便是相关的 Properties 类。
8.1 其他细节
上面中的 HttpEncodingAutoConfiguration 的源码,我们可以发现注解 @ConditionalOnWebApplication,它是 Spring 注解 @Conditional 的派生注解,其可以根据 @Conditional 指定的条件是否成立,来选择给容器中添加组件,条件成立时配置配里面的所有内容才生效。比如 @ConditionalOnWebApplication 只有在当前应用是 web 应用时配置类才会生效。
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
我们可以通过在 application.properties 中启用 debug=true
属性,来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效:
1 | ========================= |