# SpringBoot配置详解
# 配置基础
介绍Spring Boot的工程结构时,有提到过 src/main/resources目录是Spring Boot的配置目录,所以我们要为应用创建配置个性化配置时,就是在该目录之下。
Spring Boot的默认配置文件位置为: src/main/resources/application.properties。
关于Spring Boot应用的配置内容都可以集中在该文件中了,根据我们引入的不同Starter模块,
可以在这里定义诸如:容器端口名、数据库链接信息、日志级别等各种配置信息。
比如,我们需要自定义web模块的服务端口号,可以在 application.properties中添加 server.port=8888来指定服务端口为8888,也可以通过 spring.application.name=hello来指定应用名(该名字在Spring Cloud应用中会被注册为服务名)。
Spring Boot的配置文件除了可以使用传统的properties文件之外,还支持现在被广泛推荐使用的YAML文件。
YAML(英语发音:/ˈjæməl/,尾音类似camel骆驼)是一个可读性高,用来表达资料序列的格式。YAML参考了其他多种语言,包括:C语言、Python、Perl,并从XML、电子邮件的数据格式(RFC 2822)中获得灵感。Clark Evans在2001年首次发表了这种语言,另外Ingy döt Net与Oren Ben-Kiki也是这语言的共同设计者。目前已经有数种编程语言或脚本语言支援(或者说解析)这种语言。YAML是"YAML Ain't a Markup Language"(YAML不是一种标记语言)的递回缩写。在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言),但为了强调这种语言以数据做为中心,而不是以标记语言为重点,而用反向缩略语重新命名。AML的语法和其他高阶语言类似,并且可以简单表达清单、散列表,标量等资料形态。它使用空白符号缩排和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种设定档、倾印除错内容、文件大纲(例如:许多电子邮件标题格式和YAML非常接近)。尽管它比较适合用来表达阶层式(hierarchical model)的数据结构,不过也有精致的语法可以表示关联性(relational model)的资料。由于YAML使用空白字元和分行来分隔资料,使得它特别适合用grep/Python/Perl/Ruby操作。其让人最容易上手的特色是巧妙避开各种封闭符号,如:引号、各种括号等,这些符号在巢状结构时会变得复杂而难以辨认。 —— 维基百科
YAML采用的配置格式不像properties的配置那样以单纯的键值对形式来表示,而是以类似大纲的缩进形式来表示。比如:下面的一段YAML配置信息
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name:My Cool App
2
3
4
5
6
7
与其等价的properties配置如下。
environments.dev.url=http://dev.bar.com
environments.dev.name=Developer Setup
environments.prod.url=http://foo.bar.com
environments.prod.name=My Cool App
2
3
4
通过YAML的配置方式,我们可以看到配置信息利用阶梯化缩进的方式,其结构显得更为清晰易读,同时配置内容的字符量也得到显著的减少。除此之外,YAML还可以在一个单个文件中通过使用 spring.profiles属性来定义多个不同的环境配置。例如下面的内容,在指定为test环境时, server.port将使用8882端口;而在prod环境, server.port将使用8883端口;如果没有指定环境, server.port将使用8881端口。
server:
port:8880
spring:
profiles:test
server:
port:8881
spring:
profiles:prod
server:
port:8882
2
3
4
5
6
7
8
9
10
注意:YAML目前还有一些不足,它无法通过 @PropertySource注解来加载配置。但是,YAML加载属性到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。
# 自定义参数
我们除了可以在Spring Boot的配置文件中设置各个Starter模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。
比如在 application.properties中添加:
book.name=SpringCloudInAction
book.author=ZhaiYongchao
2
然后,在应用中我们可以通过 @Value注解来加载这些自定义的参数,比如:
@Component
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
// 省略getter和setter
}
2
3
4
5
6
7
8
@Value注解加载属性值的时候可以支持两种表达式来进行配置:
- 一种是我们上面介绍的PlaceHolder方式,格式为 ${...} ,大括号内为PlaceHolder
- 另外还可以使用SpEL表达式(Spring Expression Language), 格式为 #{...} ,大括号内为SpEL表达式
# 参数引用
在 application.properties中的各个参数之间,我们也可以直接通过使用PlaceHolder的方式来进行引用,就像下面的设置:
book.name=SpringCloud
book.author=ZhaiYongchao
book.desc=${book.author} is writing《${book.name}》
2
3
book.desc参数引用了上文中定义的 book.name和 book.author属性,最后该属性的值就是 ZhaiYongchaoiswriting《SpringCloud》。
# 使用随机数
在一些特殊情况下,有些参数我们希望它每次加载的时候不是一个固定的值,比如:密钥、服务端口等。在Spring Boot的属性配置文件中,我们可以通过使用 ${random}配置来产生随机的int值、long值或者string字符串,这样我们就可以容易的通过配置来属性的随机生成,而不是在程序中通过编码来实现这些逻辑。
${random}的配置方式主要有一下几种,读者可作为参考使用。
# 随机字符串
com.didispace.blog.value=${random.value}
# 随机int
com.didispace.blog.number=${random.int}
# 随机long
com.didispace.blog.bignumber=${random.long}
# 10以内的随机数
com.didispace.blog.test1={random.int(10)}
# 10-20的随机数
com.didispace.blog.test2=${random.int[10,20]}
2
3
4
5
6
7
8
9
10
该配置方式可以用于设置应用端口等场景,避免在本地调试时出现端口冲突的麻烦
# 命令行参数
回顾一下在本章的快速入门中,我们还介绍了如何启动Spring Boot应用,其中提到了使用命令 java-jar命令来启动的方式。该命令除了启动应用之外,还可以在命令行中来指定应用的参数,
比如: java-jar xxx.jar--server.port=8888,直接以命令行的方式,来设置server.port属性,另启动应用的端口设为8888。
在命令行方式启动Spring Boot应用时,连续的两个减号 --就是对 application.properties中的属性值进行赋值的标识。
所以, java-jar xxx.jar--server.port=8888命令,等价于我们在 application.properties中添加属性 server.port=8888。
通过命令行来修改属性值是Spring Boot非常重要的一个特性,
通过此特性,理论上已经使得我们应用的属性在启动前是可变的,所以其中端口号也好、数据库连接也好,都是可以在应用启动时发生改变,而不同于以往的Spring应用通过Maven的Profile在编译器进行不同环境的构建。其最大的区别就是,Spring Boot的这种方式,可以让应用程序的打包内容,贯穿开发、测试以及线上部署,而Maven不同Profile的方案每个环境所构建的包,其内容本质上是不同的。但是,如果每个参数都需要通过命令行来指定,这显然也不是一个好的方案,所以下面我们看看如果在Spring Boot中实现多环境的配置。
# 多环境配置
我们在开发任何应用的时候,通常同一套程序会被应用和安装到几个不同的环境,比如:开发、测试、生产等。其中每个环境的数据库地址、服务器端口等等配置都会不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常繁琐且容易发生错误的事。
对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,Spring Boot也不例外,或者说更加简单。
在Spring Boot中多环境配置文件名需要满足 application-{profile}.properties的格式,其中 {profile}对应你的环境标识,比如:
- application-dev.properties:开发环境
- application-test.properties:测试环境
- application-prod.properties:生产环境
至于哪个具体的配置文件会被加载,需要在 application.properties文件中通过 spring.profiles.active属性来设置,其值对应配置文件中的 {profile}值。
如: spring.profiles.active=test就会加载 application-test.properties配置文件内容。
下面,以不同环境配置不同的服务端口为例,进行样例实验。
针对各环境新建不同的配置文件 application-dev.properties、 application-test.properties、application-prod.properties
在这三个文件均都设置不同的 server.port属性,如:dev环境设置为1111,test环境设置为2222,prod环境设置为3333
application.properties中设置 spring.profiles.active=dev,就是说默认以dev环境设置
测试不同配置的加载
执行 java-jar xxx.jar,可以观察到服务端口被设置为 1111,也就是默认的开发环境(dev)
执行 java-jar xxx.jar--spring.profiles.active=test,可以观察到服务端口被设置为 2222,也就是测试环境的配置(test)
执行 java-jar xxx.jar--spring.profiles.active=prod,可以观察到服务端口被设置为 3333,也就是生产环境的配置(prod)
按照上面的实验,可以如下总结多环境的配置思路:
application.properties中配置通用内容,并设置 spring.profiles.active=dev,以开发环境为默认配置
application-{profile}.properties中配置各个环境不同的内容.
通过命令行方式去激活不同环境的配置
# 加载顺序
在上面的例子中,我们将Spring Boot应用需要的配置内容都放在了项目工程中,虽然我们已经能够通过 spring.profiles.active或是通过Maven来实现多环境的支持。
但是,当我们的团队逐渐壮大,分工越来越细致之后,往往我们不需要让开发人员知道测试或是生成环境的细节,而是希望由每个环境各自的负责人(QA或是运维)来集中维护这些信息。
那么如果还是以这样的方式存储配置内容,对于不同环境配置的修改就不得不去获取工程内容来修改这些配置内容,当应用非常多的时候就变得非常不方便。同时,配置内容都对开发人员可见,本身这也是一种安全隐患。
对此,现在出现了很多将配置内容外部化的框架和工具,后续将要介绍的Spring Cloud Config就是其中之一,为了后续能更好的理解Spring Cloud Config的加载机制,我们需要对Spring Boot对数据文件的加载机制有一定的了解。
Spring Boot为了能够更合理的重写各属性的值,使用了下面这种较为特别的属性加载顺序:
- 命令行中传入的参数。
SPRING_APPLICATION_JSON中的属性。SPRING_APPLICATION_JSON是以JSON格式配置在系统环境变量中的内容。- java:comp/env中的 JNDI属性。
- Java的系统属性,可以通过 System.getProperties()获得的内容。
- 操作系统的环境变量
- 通过 random.*配置的随机属性
- 位于当前应用jar包之外,针对不同 {profile}环境的配置文件内容,例如: application-{profile}.properties或是 YAML定义的配置文件
- 位于当前应用jar包之内,针对不同 {profile}环境的配置文件内容,例如: application-{profile}.properties或是 YAML定义的配置文件
- 位于当前应用jar包之外的 application.properties和 YAML配置内容
- 位于当前应用jar包之内的 application.properties和 YAML配置内容
- 在
@Configuration注解修改的类中,通过@PropertySource注解定义的属性 应用默认属性,使用 SpringApplication.setDefaultProperties定义的内容 - 优先级按上面的顺序有高到低,数字越小优先级越高。
可以看到,其中第7项和第9项都是从应用jar包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代jar包之内的配置内容。
通过这样的实现,我们的工程在配置中就变的非常干净,我们只需要在本地放置开发需要的配置即可,而其他环境的配置就可以不用关心,由其对应环境的负责人去维护即可。
# bootstrap和application的优先级
TIP
bootstrap配置文件由spring父上下文加载,并且比application配置文件优先加载(父上下文不会使用application配置文件),而application配置文件由子上下文加载。bootstrap加载的配置信息不能被application的相同配置覆盖。
但是注意,如果要使用配置文件中的变量,那么同名变量将使用application文件中的配置,比如如果两个配置文件都有server.post变量,那么Spring将使用application中配置的值。为什么?因为在Environment中,application配置文件的propertySource排在bootstrap配置文件的propertySource之前,Spring 在进行属性注入、获取时,将会顺序遍历所有的propertySource查找属性,如果找到了就直接返回。.peoperties文件比.yaml文件的属性查找优先级更高的原理一样。
# 2.x 新特性
在Spring Boot 2.0中推出了Relaxed Binding 2.0,对原有的属性绑定功能做了非常多的改进以帮助我们更容易的在Spring应用中加载和读取配置信息。下面本文就来说说Spring Boot 2.0中对配置的改进。
# 配置文件绑定
# 简单类型
在Spring Boot 2.0中对配置属性加载的时候会除了像1.x版本时候那样移除特殊字符外,还会将配置均以全小写的方式进行匹配和加载。
所以,下面的4种配置方式都是等价的:
#properties格式:
spring.jpa.databaseplatform=mysql
spring.jpa.database-platform=mysql
spring.jpa.databasePlatform=mysql
spring.JPA.database_platform=mysql
2
3
4
5
Tips:推荐使用全小写配合 -分隔符的方式来配置,比如:
spring.jpa.database-platform=mysql
# List类型
在properties文件中使用 []来定位列表类型,比如:
spring.my-example.url[0]=http://example.com
spring.my-example.url[1]=http://spring.io
2
也支持使用逗号分割的配置方式,上面与下面的配置是等价的:
spring.my-example.url=http://example.com,http://spring.io
而在yaml文件中使用可以使用如下配置:
spring:
my-example:
url:
- http://example.com
- http://spring.io
2
3
4
5
spring:
my-example:
url:http://example.com,http://spring.io
2
3
注意:在Spring Boot 2.0中对于
List类型的配置必须是连续的,不然会抛出UnboundConfigurationPropertiesException异常,所以如下配置是不允许的:
foo[0]=a
foo[2]=b
2
在
Spring Boot 1.x中上述配置是可以的, foo[1]由于没有配置,它的值会是null
# 配置文件对象化
在属性非常多的情况下,也可以定义一个和配置文件对应的Bean
@Getter
@Setter
@ConfigurationProperties(prefix = "self.define")
@PropertySource("classpath:selfDefine.properties")
@Component
public class SelfDefineProperties {
private String devName;
private String devSex;
}
2
3
4
5
6
7
8
9
我们还可以把@ConfigurationProperties还可以直接定义在@bean的注解上,这是bean实体类就不用@Component和@ConfigurationProperties了
@SpringBootApplication
public class ShowApplication{
@Bean
@ConfigurationProperties(prefix = "self.define")
public ConnectionSettings connectionSettings(){
return new ConnectionSettings();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
2.1.6RELEASE版需要引入如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
# 日志输出
#日志
logging.file=logs/ffCamera.log
logging.level.root=info
logging.path=d:/logs/my.log
2
3
4
path与file同时存在时,以file为准
# logback输出到文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<contextName>SpringBoot-logBack-demo</contextName>
<!-- 文件输出格式 -->
<property name="PATTERN" value="%-12(%d{yyyy-MM-dd HH:mm:ss.SSS}) |-%-5level %contextName [%thread] %c [%L] -| %msg%n" />
<!-- test文件路径 -->
<property name="TEST_FILE_PATH" value="F:/WORKSPACE/IDEA_WORKSPACE/MICRO_COURSES/springBootLogging/target/logs" />
<!-- pro文件路径 -->
<property name="PRO_FILE_PATH" value="F:/WORKSPACE/IDEA_WORKSPACE/MICRO_COURSES/springBootLogging/target/logs" />
<property name="LOG_FILE_SIZE" value="100MB"/>
<!-- 每天产生一个文件 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<!--对应info级别,文件名以info-xxx.log形式命名,每天产生一个文件-->
<appender name="INFOFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<file>${TEST_FILE_PATH}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 文件名称 -->
<fileNamePattern>${TEST_FILE_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 只保留最近30天的日志 -->
<MaxHistory>30</MaxHistory>
<!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,命名日志文件,例如info-2018-06-10.0.log -->
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${LOG_FILE_SIZE}</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>-->
<encoder>
<charset>UTF-8</charset>
<pattern>${PATTERN}</pattern>
</encoder>
<!--如果只想要INFO级别的日志,使用如下策略即可只输出INFO级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤INFO-->
<level>INFO</level>
<!--匹配到就允许-->
<onMatch>ACCEPT</onMatch>
<!--没有匹配到就允许-->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--对应info级别,文件名以error-xxx.log形式命名 每天产生一个文件-->
<appender name="ERRORFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--日志名称,如果没有File 属性,那么只会使用FileNamePattern的文件路径规则
如果同时有<File>和<FileNamePattern>,那么当天日志是<File>,明天会自动把今天
的日志改名为今天的日期。即,<File> 的日志都是当天的。
-->
<file>${TEST_FILE_PATH}/error.log</file>
<!--滚动策略,按照时间滚动 TimeBasedRollingPolicy-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 文件名称 -->
<fileNamePattern>${TEST_FILE_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 只保留最近30天的日志 -->
<MaxHistory>30</MaxHistory>
<!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,命名日志文件,例如error-2018-06-10.0.log -->
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>${LOG_FILE_SIZE}</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<!--<totalSizeCap>1GB</totalSizeCap>-->
</rollingPolicy>
<!--<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${PATTERN}</pattern>
</layout>-->
<encoder>
<charset>UTF-8</charset>
<pattern>${PATTERN}</pattern>
</encoder>
<!--如果只想要ERROR级别的日志,使用如下策略即可只输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--过滤INFO-->
<level>ERROR</level>
<!--匹配到就允许-->
<onMatch>ACCEPT</onMatch>
<!--没有匹配到就允许-->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--具体个性化某个Controller或者某个包下使用指定级别的日志输出-->
<logger name="com.black.example.*" level="INFO"/>
<logger name="org.springframework.web" level="DEBUG"/>
<!--测试环境-->
<springProfile name="test">
<!--<root level="TRACE">-->
<root level="INFO">
<appender-ref ref="ERRORFILE" />
<appender-ref ref="INFOFILE"/>
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
<!--开发环境-->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="ERRORFILE" />
<appender-ref ref="INFOFILE"/>
</root>
</springProfile>
<!--生产环境-->
<springProfile name="pro">
<root level="ERROR">
<appender-ref ref="ERRORFILE" />
<appender-ref ref="INFOFILE"/>
</root>
</springProfile>
</configuration>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#主配置文件,配置了这个会优先读取里面的属性覆盖主配置文件的属性
spring.profiles.active=dev
##自定义配置文件名称
logging.config=classpath:logback-black.xml
启动服务时指定配置加载:
java -jar xxx.jar –spring.profiles.active=prod
2
3
4
5
6
7
8
如果以war包形式部署到外部tomcat则需做如下设置
<property name="log.path" value="../logs"/>,即../logs,则日志文件位置为tomcat目录
# 自动配置原理
# 注解拾遗
# @Value
@Value就相当于传统 xml 配置文件中的 value 字段。
我们知道配置文件中的 value 的取值可以是:
- 字面量
- 通过
${key}方式从环境变量中获取值 - 通过
${key}方式全局配置文件中获取值 #{SpEL}
# @ConfigurationProperties
标有 @ConfigurationProperties 的类的所有属性和配置文件中相关的配置项进行绑定。(默认从全局配置文件中获取配置值),绑定之后我们就可以通过这个类去访问全局配置文件中的属性值了。
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private String sex;
}
2
3
4
5
6
7
8
# @Import
- 直接导入普通的 Java 类。
//1.创建一个普通的 Java 类。
public class Circle {
public void sayHi() {
System.out.println("Circle sayHi()");
}
}
//2.创建一个配置类,里面没有显式声明任何的 Bean,然后将刚才创建的 Circle 导入。
@Import({Circle.class})
@Configuration
public class MainConfig {
}
//3.创建测试类。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
Circle circle = context.getBean(Circle.class);
circle.sayHi();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 配合自定义的 ImportSelector 使用。
ImportSelector 是一个接口,该接口中只有一个 selectImports 方法,用于返回全类名数组。所以利用该特性我们可以给容器动态导入 N 个 Bean。
//1.创建普通 Java 类 Triangle。
public class Triangle {
public void sayHi(){
System.out.println("Triangle sayHi()");
}
}
//2.创建 ImportSelector 实现类,selectImports 返回 Triangle 的全类名。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"annotation.importannotation.waytwo.Triangle"};
}
}
//3.创建配置类,在原来的基础上还导入了 MyImportSelector。
@Import({Circle.class,MyImportSelector.class})
@Configuration
public class MainConfigTwo {
}
//4.创建测试类
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigTwo.class);
Circle circle = context.getBean(Circle.class);
Triangle triangle = context.getBean(Triangle.class);
circle.sayHi();
triangle.sayHi();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- 配合 ImportBeanDefinitionRegistrar 使用。
ImportBeanDefinitionRegistrar也是一个接口,它可以手动注册bean到容器中,从而我们可以对类进行个性化的定制。(需要搭配@Import与@Configuration一起使用。)
//1.创建普通 Java 类 Rectangle。
public class Rectangle {
public void sayHi() {
System.out.println("Rectangle sayHi()");
}
}
//2.创建 ImportBeanDefinitionRegistrar 实现类,实现方法直接手动注册一个名叫 rectangle 的 Bean 到 IOC 容器中。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rectangle.class);
// 注册一个名字叫做 rectangle 的 bean
beanDefinitionRegistry.registerBeanDefinition("rectangle", rootBeanDefinition);
}
}
//3.创建配置类,导入 MyImportBeanDefinitionRegistrar 类。
@Import({Circle.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class MainConfigThree {
}
//4.创建测试类。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigThree.class);
Circle circle = context.getBean(Circle.class);
Triangle triangle = context.getBean(Triangle.class);
Rectangle rectangle = context.getBean(Rectangle.class);
circle.sayHi();
triangle.sayHi();
rectangle.sayHi();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# @Conditional
@Conditional 注释可以实现只有在特定条件满足时才启用一些配置
//1.创建普通 Java 类 ConditionBean,该类主要用来验证 Bean 是否成功加载。
public class ConditionBean {
public void sayHi() {
System.out.println("ConditionBean sayHi()");
}
}
//2.创建 Condition 实现类,@Conditional 注解只有一个 Condition 类型的参数,Condition 是一个接口,该接口只有一个返回布尔值的 matches() 方法,该方法返回 true 则条件成立,配置类生效。反之,则不生效。在该例子中我们直接返回 true。
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return true;
}
}
//3.创建配置类,可以看到该配置的 @Conditional 传了我们刚才创建的 Condition 实现类进去,用作条件判断。
@Configuration
@Conditional(MyCondition.class)
public class ConditionConfig {
@Bean
public ConditionBean conditionBean(){
return new ConditionBean();
}
}
//4.编写测试方法。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ConditionConfig.class);
ConditionBean conditionBean = context.getBean(ConditionBean.class);
conditionBean.sayHi();
}
//5.结果分析
//因为 Condition 的 matches 方法直接返回了 true,配置类会生效,我们可以把 matches 改成返回 false,则配置类就不会生效了。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| 扩展注解 | 作用 |
|---|---|
ConditionalOnBean | 容器中存在指定的Bean,则生效 |
ConditionalOnMissingBean | 容器中不存在指定的Bean,则生效 |
ConditionalOnClass | 系统中有指定的类,则生效 |
ConditionalOnMissingClass | 系统中没有指定的类,则生效 |
ConditionalOnProperty | 系统中指定的属性是否有指定的值,则生效 |
ConditionalOnWebApplication | 当前时web开发环境,则生效 |
# 配置上传请求最大数据
#springboot2.X文件上传大小限制
spring.servlet.multipart.max-file-size=1024MB
spring.servlet.multipart.max-request-size=1024MB
2
3
# 配置元数据指南
# 启动过程
- ApplicationContextInitializer
- ApplicationRunner
- CommandLineRunner
- SpringApplicationRunListener
# 数据库配置
- oracle数据库配置
#spring-boot配置oracle
spring.datasource.url=jdbc:oracle:thin:@192.168.25.231:1521:orcl
spring.datasource.username=faitherp
spring.datasource.password=faitherp
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
#引入jar包:ojdbc6.jar,pom中配置的话,无法下载,需手动添加
2
3
4
5
6
# 2-1、多数据源
# @ConfigurationProperties新解析
- 使用ConfigurationProperties配置模块
- 激活ConfigurationProperties
- 无法转换的属性
- 未知的属性
- 启动时校验ConfigurationProperties
- 使用 Spring Boot Configuration Processor 完成自动补全
- 标记配置属性为 Deprecated
- 附加说明
在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过
@ConfigurationProperties注解,我们可以方便的获取这些参数值
# 使用ConfigurationProperties配置模块
#常用公共字符串
self.prop.status-success=SUCCESS
self.prop.status-fail=FAIL
self.prop.status-exception=EXCEPTION
2
3
4
我们可以使用 @Value 注解或着使用 Spring Environmentbean 访问这些属性,是这种注入配置方式有时显得很笨重。我们将使用更安全的方式(@ConfigurationProperties )来获取这些属性
@Component
@ConfigurationProperties(prefix = "self.prop")
@Getter
@Setter
public class CustomerProperties {
/**
* 操作成功返回信息
*/
private String statusSuccess;
/**
* 操作失败返回信息
*/
private String statusFail;
/**
* 异常返回信息
*/
private String statusException;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 请注意以下几点
- 前缀定义了哪些外部属性将绑定到类的字段上
- 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
- 我们可以简单地用一个值初始化一个字段来定义一个默认值
- 类本身可以是包私有的
- 类的字段必须有公共 setter 方法
# Spring宽松绑定规则(relaxedBinding)
Spring使用一些宽松的绑定属性规则。因此,以下变体都将绑定到 statusSuccess 属性上:
self.prop.statusSuccess=SUCCESS
self.prop.statussuccess=SUCCESS
self.prop.status-success=SUCCESS
self.prop.status_success=SUCCESS
self.prop.STATUS_SUCCESS=SUCCESS
2
3
4
5
# 激活ConfigurationProperties
我们可以通过下面几种方式将其添加到应用上下文中
- 通过添加
@Component注解让Component Scan扫描到
@Component
@ConfigurationProperties(prefix = "self.prop")
@Getter
@Setter
public class CustomerProperties {
}
2
3
4
5
6
- 通过 Spring 的 Java Configuration 特性实现同样的效果
@Configuration
class PropertiesConfig{
@Bean
public CustomerProperties customerProperties(){
return new CustomerProperties();
}
}
2
3
4
5
6
7
8
- 使用 @EnableConfigurationProperties 注解让我们的类被 Spring Boot 所知道
- 启动类增加该注解
- 配置类增加该注解
@SpringBootApplication
@MapperScan(value= {"com.ffCamera.mapper"})
@EnableScheduling
@EnableConfigurationProperties({CustomerProperties.class})
@Slf4j
public class FfCameraApplication extends SpringBootServletInitializer implements ApplicationRunner {
}
2
3
4
5
6
7
或者
@Configuration
@EnableConfigurationProperties({CustomerProperties.class})
class PropertiesConfig{
}
2
3
4
# 激活一个ConfigurationProperties类的最佳方式是什么
特定于模块的 @Configuration 类上使用@EnableConfigurationProperties
# 无法转换的属性
如果我们在 application.properties 属性上定义的属性不能被正确的解析会发生什么?假如我们为原本应该为
布尔值的属性提供的值为 'foo':
默认情况下,Spring Boot 将会启动失败,并抛出异常
当我们为属性配置错误的值时,而又不希望 Spring Boot 应用启动失败,我们可以设置 ignoreInvalidFields 属性为 true (默认为 false)
# 未知的属性
如果我们在 application.properties 文件提供了 MailModuleProperties 类不知道的属性会发生什么?
默认情况下,Spring Boot 会忽略那些不能绑定到 @ConfigurationProperties 类字段的属性
然而,当配置文件中有一个属性实际上没有绑定到 @ConfigurationProperties 类时,我们可能希望启动失败。也许我们以前使用过这个配置属性,但是它已经被删除了,这种情况我们希望被触发告知手动从 application.properties 删除这个属性
为了实现上述情况,我们仅需要将 ignoreUnknownFields 属性设置为 false (默认是 true)
# 启动时校验ConfigurationProperties
如果我们希望配置参数在传入到应用中时有效的,我们可以通过在字段上添加 bean validation(
如@NotNull等) 注解,同时在类上添加@Validated注解
当然这些默认的验证注解不能满足你的验证要求,我们也可以自定义注解
如果你的验证逻辑很特殊,我们可以实现一个方法,并用 @PostConstruct 标记,如果验证失败,方法抛出异常即可
# 复杂属性类型
# List
有时我们需要传递诸如 List 的数据类型
- 两种方式让 Spring Boot 自动填充该 list 属性
在 application.properties 文件中以数组形式书写
YAML 本身支持 list 类型
# Duration
@Component
@ConfigurationProperties(prefix = "self.prop")
@Getter
@Setter
public class CustomerProperties {
@DurationUnit(ChronUnit.SECONDS)
private Duration selfDuration;
}
2
3
4
5
6
7
8
我们既可以配置毫秒数数值,也可配置带有单位的文本:
- ns for nanoseconds (纳秒)
- us for microseconds (微秒)
- ms for milliseconds (毫秒)
- s for seconds (秒)
- m for minutes (分)
- h for hours (时)
- d for days (天)
self.prop.self-duration=5s
配置 duration 不写单位,默认按照毫秒来指定,我们也可已通过 @DurationUnit 来指定单位:
# DataSize
与 Duration 的用法一毛一样,默认单位是 byte (字节),可以通过 @DataSizeUnit 单位指定。
但是,我测试的时候打印出来结果都是以 B (bytes) 来显示
常见单位如下:
- B for bytes
- KB for kilobytes
- MB for megabytes
- GB for gigabytes
- TB for terabytes
# 自定义类型
有些情况,我们想解析配置参数到我们自定义的对象类型上,假设,我们我们设置最大包裹重量
self.prop.max-weight=5kg
- 我们可以模仿 DataSize 和 Duration 创造自己的 converter (转换器)
@ConfigurationPropertiesBinding注解是让 Spring Boot 知道使用该转换器做数据绑定
# processor
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
重新 build 项目之后,configuration processor 会为我们创建一个 JSON 文件(路径:target/classes/META-INF)
当我们在 application.properties 和 application.yml 中写配置的时候会有自动提醒
# Deprecated
我们可以通过添加 @DeprecatedConfigurationProperty注解到字段的 getter 方法上,来标示该字段为 deprecated,重新 build 项目,看看 JSON 文件发生了什么
# 附加说明
- 如果使用
SpEL表达式,我们只能选择 @Value 注解