- 推荐序一
- 推荐序二
- 推荐序三
- 推荐语
- 前言
- 第1章 基础知识
- 第2章 微服务构建:Spring Boot
- 第3章 服务治理:Spring Cloud Eureka
- 第4章 客户端负载均衡:Spring Cloud Ribbon
- 第5章 服务容错保护:Spring Cloud Hystrix
- 第6章 声明式服务调用:Spring Cloud Feign
- 第7章 API网关服务:Spring Cloud Zuul
- 第8章 分布式配置中心:Spring Cloud Config
- 第9章 消息总线:Spring Cloud Bus
- 第10章 消息驱动的微服务:Spring Cloud Stream
- 附录 A Starter POMs
- 后记
配置详解
在上一节中,我们轻松地实现了一个简单的RESTful API应用,体验了Spring Boot的诸多优点。我们用非常少的代码就成功实现了一个Web应用,这是传统Spring应用无法办到的。虽然在实现Controller时用到的代码是一样的,但是在配置方面,相信大家也注意到了,在上面的例子中,除了Maven的配置之外,没有引入任何其他配置。
这就是之前我们提到的,Spring Boot针对常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。但是,我们还是需要了解如何在Spring Boot中修改这些自动化的配置内容,以应对一些特殊的场景需求,比如,我们在同一台主机上需要启动多个基于Spring Boot的Web应用,若不为每个应用指定特别的端口号,那么默认的8080端口必将导致冲突。
后续我们在使用Spring Cloud的各个组件的时候,其实有大量的工作都会是针对配置文件的。所以我们有必要深入了解一些关于Spring Boot中的配置文件的知识,比如配置方式、如何实现多环境配置、配置信息的加载顺序等。
配置文件
在快速入门示例中,我们介绍 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(仍是一种标记语言),但为了强调这种语言以数据作为中心,而不是以标记语言为重点,而用反向缩略语重新命名。YAML的语法和其他高阶语言类似,并且可以简单地表达清单、散列表、标量等形态。它使用空白符号缩排和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种设定文档、文件大纲(例如,许多电子邮件标题格式和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
与其等价的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
通过YAML的配置方式我们可以看到,配置信息利用阶梯化缩进的方式,其结构更为清晰易读,同时配置内容的字符量也得到显著减少。除此之外,YAML还可以在一个单个文件中通过使用spring.profiles属性来定义多个不同的环境配置。例如下面的内容,在指定为test环境时,server.port将使用8882端口;而在prod环境中,server.port将使用8883端口;如果没有指定环境,server.port将使用8881端口。
server:
port: 8881
---
spring:
profiles: test
server:
port: 8882
---
spring:
profiles: prod
server:
port: 8883
- 注意 YAML目前还有一些不足,它无法通过@PropertySource注解来加载配置。但是,YAML将属性加载到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。
自定义参数
除了可以在Spring Boot的配置文件中设置各个Starter模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。比如在application.properties中添加:
book.name=SpringCloudInAction
book.author=ZhaiYongchao
然后,在应用中可以通过@Value注解来加载这些自定义的参数,比如:
@Component
public class Book {
@Value("${book.name}")
private String name;
@Value("${book.author}")
private String author;
//省略getter和setter
}
@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}》
book.desc参数引用了上文中定义的book.name和book.author属性,最后该属性的值就是ZhaiYongchao is writing《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]}
该配置方式可以设置应用端口等场景,以避免在本地调试时出现端口冲突的麻烦。
命令行参数
回顾一下在本章的“快速入门”小节中,我们还介绍了如何启动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使用了下面这种较为特别的属性加载顺序:
1.在命令行中传入的参数。
2.SPRING_APPLICATION_JSON 中的属性。SPRING_APPLICATION_JSON 是以JSON格式配置在系统环境变量中的内容。
3.java:comp/env中的JNDI属性。
4.Java的系统属性,可以通过System.getProperties()获得的内容。
5.操作系统的环境变量。
6.通过random.*配置的随机属性。
7.位于当前应用 jar 包之外,针对不同{profile}环境的配置文件内容,例如application-{profile}.properties或是YAML定义的配置文件。
8.位于当前应用 jar 包之内,针对不同{profile}环境的配置文件内容,例如application-{profile}.properties或是YAML定义的配置文件。
9.位于当前应用jar包之外的application.properties和YAML配置内容。
10.位于当前应用jar包之内的application.properties和YAML配置内容。
11.在@Configuration注解修改的类中,通过@PropertySource注解定义的属性。
12.应用默认属性,使用 SpringApplication.setDefaultProperties 定义的内容。
优先级按上面的顺序由高到低,数字越小优先级越高。
可以看到,其中第7项和第9项都是从应用jar包之外读取配置文件,所以,实现外部化配置的原理就是从此切入,为其指定外部配置文件的加载位置来取代jar包之内的配置内容。通过这样的实现,我们的工程在配置中就变得非常干净,只需在本地放置开发需要的配置即可,而不用关心其他环境的配置,由其对应环境的负责人去维护即可。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论