在这里介绍SpringBoot的目的除了它是SpringCloud的基础之外,也由于其自身的各项优点,如自动化配置、快速开发、轻松部署等,非常适合作为微服务架构中各项具体微服务的开发框架。所以我们强烈推荐使用SpringBoot来构建微服务,它不仅可以帮助我们快速地构建微服务,还可以轻松简单地整合SpringCloud实现系统服务化,而如果使用了传统的Spring构建方式的话,在整合过程中我们还需要做更多的依赖管理工作才能让它们完好地运行起来。
SpringBoot的宗旨并非要重写Spring或是替代Spring,而是希望通过设计大量的自动化配置等方式来简化Spring原有样板化的配置,使得开发者可以快速构建应用。除了解决配置问题之外,SpringBoot还通过一系列Stater POMs
的定义,让我们整合各项功能的时候,不需要在Maven的pom.xml中维护那些错综复杂的依赖关系,而是通过类似模块化的Starter模块定义来引用,使得依赖管理工作变得更为简单。
在如今容器化大行其道的时代,SpringBoot除了可以很好融入Docker之外,其自身就支持嵌入式的Tomcat、Jetty等容器。所以,通过SpringBoot构建的应用不再需要安装Tomcat,将应用打包成war,再部署到Tomcat这样复杂的构建与部署动作,只需将SpringBoot应用打成jar包,并通过java -jar
命令直接运行就能启动一个标准化的Web应用,这使得SpringBoot应用变得非常轻便。
SpringBoot对于构建、部署等做了这么多的优化,自然不能少了对开发环节的优化。整个SpringBoot的生态系统都使用到了Groovy,很自然的,我们完全可以通过使用Gradle和Groovy来开发SpringBoot应用。
<dependency>
<groupid>org.springfrarnework.bootgroupId>
<artifactid>spring-boot-starter-testartifactid>
<scope>七estscope>
dependency>
在使用SpringBoot构建应用的时候,各项功能模块的整合不再像传统Spring应用的开发方式那样,需要在 pom.xml中做大量的依赖配置,而是通过使用Starter POMs定义的依赖包,使得功能模块整合变得非常轻巧,易于理解与使用。SpringBoot针对常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。
src/rnain/resources
目录是SpringBoot的配置目录,所以当要为应用创建个性化配置时,应在该目录下进行。SpringBoot的默认配置文件位置为src/main/resources/application.properties
。关于SpringBoot应用的配置内容都可以集中在该文件中,根据我们引入的不同Starter模块,可以在这里定义容器端口号、数据库连接信息、日志级别等各种配置信息。SpringBoot的配置文件除了可以使用传统的properties文件之外,还支持现在被广泛推荐使用的YAML文件。
YAML中配置信息利用阶梯化缩进的方式,其结构更为清晰易读,同时配置内容的字符量也得到显著减少。除此之外,YAML还可以在一个单个文件中通过使用spring.profiles属性来定义多个不同的环境配置。如下配置文件中在指定为 test 环境时, server.port 将使用8081端口;而在 prod 环境中, server.port将使用8082端口;如果没有指定环境, server.port将使用8080端口。
server:
port: 8080
---
spring:
profiles: test
server:
port: 8081
---
spring:
profiles: prod
server:
port: 8082
YAML目前还有—些不足,它无法通过@PropertySource注解来加载配置。但是,YAML将属性加载到内存中保存的时候是有序的,所以当配置文件中的信息需要具备顺序含义时,YAML的配置方式比起properties配置文件更有优势。
除了可以在SpringBoot的配置文件中设置各个Starter模块中预定义的配置属性,也可以在配置文件中定义一些我们需要的自定义属性。比如在application.properties中添加
book.name=SpringCloudinAction
book.author=sun
然后,在应用中可以通过@Value
注解来加载这些自定义的参数
@Value("${book.name}")
private String name;
@Value注解加载属性值的时候可以支持两种表达式来进行配置,如下所示:
${...}
,大括号内为PlaceHolder。#{...}
,大括号内为SpEL表达式。在application.properties中的各个参数之间可以直接通过使用PlaceHolder的方式来来进行引用:
book.name=SpringCloud
book.author=ZhaiYongchao
book.desc=${book.author} is writing ${book.name}
在一些特殊情况下,我们希望有些参数每次被加载的时候不是一个固定的值,比如密钥、服务端口等。在SpirngBoot的属性配置文件中,可以通过使用${random}
配置来产生随机的int值、long值或者string字符串,这样我们就可以容易地通过配置随机生成属性,而不是在程序中通过编码来实现这些逻辑。
#随机字符串
com.didispace.blog.value=${random.value}
#随机int
com.didispace.blog.number=${random.int}
#随机long
com.didispace.blog.bignumber=${random.long}
# 10以内的随机数
com.didispace.blog.tes七 1=${random.int(l0)}
# 10-20的随机数
com.didispace.blog.test2=${random.int[l0,20]}
java -jar
该命令除了启动应用之外,还可以在命令行中指定应用的参数,比如java -jar xxx.jar --server.port=8888
,直接以命
令行的方式来设置server.port属性,并将启动应用的端口设为8888。在用命令行方式启动SpringBoot应用时,连续的两个减号–就是对application.properties中的属性值进行赋值的标识。所以java -jar xxx.jar --server.port=8888
命令,等价与在application.properties中添加属性server.port=8888。
通过命令行来修改属性值是SpringBoot非常重要的一个特性。通过此特性,理论上已经使得应用的属性在启动前是可变的,所以其中的端口号也好、数据库连接也好,都是可以在应用启动时发生改变的,而不同于以往的Spring应用通过Maven的Profile在编译器中进行不同环境的构建。SpringBoot的这种方式,可以让应用程序的打包内容贯穿开发、测试以及线上部署,而Maven不同Profile的方案为每个环境所构建的包其内容本质上是不同的。但是如果每个参数都需要通过命令行来指定,这显然也不是一个好的方案,所以下面我们看看如何在SpringBoot中实现多环境的配置。
我们在开发应用的时候,通常同一套程序会被应用和安装到几个不同的环境中,比如开发、测试、生产等。其中每个环境的数据库地址、服务器端口等配置都不同,如果在为不同环境打包时都要频繁修改配置文件的话,那必将是个非常烦琐且容易发生错误的事。对于多环境的配置,各种项目构建工具或是框架的基本思路是一致的,通过配置多份不同环境的配置文件,再通过打包命令指定需要打包的内容之后进行区分打包,SpringBoot也不例外,或者说实现起来更加简单。
在SpringBoot中,多环境配置的文件名需要满足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.properties
中配置通用内容,并通过设置spring.profiles.active=xxx
或通过命令行方式去激活不同环境的配置。application-{profile}.properties
中配置各个环境不同的内容。我们可以将SpringBoot应用需要的配置内容都放在项目工程中,然后通过spring.profiles.active或是通过Maven来实现多环境的支持。但是,当团队逐渐壮大,分工越来越细致之后,往往不需要让开发人员知道测试或是生产环境的细节,而是希望由每个环境各自的负责人(QA或是运维)来集中维护这些信息。那么如果还是以这样的方式存储配置内容,对于不同环境配置的修改就不得不去获取工程内容然后来修改这些配置内容,当应用非常多的时候就变得非常不方便。同时,配置内容对开发人员都可见,这本身也是一种安全隐患。对此,出现了很多将配置内容外部化的框架和工具,后续将要介绍的SpringCloud Config
就是其中之一,为了后续能更好地理解SpringCloud Config
的加载机制,我们需要对SpringBoot对数据文件的加载机制有一定的了解。
为了能够更合理地重写各属性的值,SpringBoot使用了下面这种较为特别的属性加载顺序:
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包之内的配置内容。通过这样的实现,我们的工程在配置中就变得非常干净,只需在本地放置开发需要的配置即可,而不用关心其他环境的配置,由其对应环境的负责人去维护即可。
在微服务架构中,我们将原本庞大的单体系统拆分成多个提供不同服务的应用。虽然各个应用的内部逻辑因分解而得以简化,但是由于部署应用的数量成倍增长,使得系统的维护复杂度大大提升。对于运维人员来说,随着应用的不断增多,系统集群中出现故障的频率也变得越来越高,虽然在高可用机制的保护下,个别故障不会影响系统的对外服务,但是这些频繁出现的故障需要被及时发现和处理才能长期保证系统处于健康可用状态。为了能对这些成倍增长的应用做到高效运维,传统的运维方式显然是不合适的,所以我们需要实现一套自动化的监控运维机制,而这套机制的运行基础就是不间断地收集各个微服务应用的各项指标情况,并根据这些基础指标信息来制定监控和预警规则,更进一步甚至做到一些自动化的运维操作等。为了让运维系统能够获取各个微服务应用的相关指标以及实现一些常规操作控制,我们需要开发一套专门用于植入各个微服务应用的接口供监控系统采集信息。而这些接口往往有很大一部分指标都是类似的,比如环境变量、垃圾收集信息、内存信息、线程池信息等。既然这些信息那么通用,难道就没有一个标准化的实现框架吗?当我们决定用SpringBoot来作为微服务框架时,除了它强大的快速开发功能之外,还因为它在Starter POMs中提供了一个特殊依赖模块spring-boot-starter-actuator
。引入该模块能够自动为SpringBoot构建的应用提供一系列用于监控的端点。同时,SpringCloud在实现各个微服务组件的时候,进一步为该模块做了不少扩展,比如,为原生端点增加了更多的指标和度量信息(比如在整合Eureka的时候会为/health端点增加相关的信息),并且根据不同的组件还提供了更多有空的端点(比如,为API网关组件Zuul提供了/routes端点来返回路由信息)。
spring-boot-starter-actuator
模块的实现对于实施微服务的中小团队来说,可以有效地省去或大大减少监控系统在采集应用指标时的开发量。当然它也并不是万能的,有时候也需要对其做一些简单的扩展来帮助我们实现自身系统个性化的监控需求。
spring-boot-starter-actuator
模块中已经实现的一些原生端点。 根据端点的作用,可以将原生端点分为以下三大类:
应用配置类:获取应用程序中加载的应用配置、环境变量、自动化配置报告等与SpringBoot应用密切相关的配置类信息。
由于SpringBoot为了改善传统Spring应用繁杂的配置内容,采用了包扫描和自动化配置的机制来加载原本集中于XML文件中的各项内容。虽然这样的做法让我们的代码变得非常简洁,但是整个应用的实例创建和依赖关系等信息都被离散到了各个配置类的注解上,这使我们分析整个应用中资源和实例的各种关系变得非常困难。而这类端点可以帮助我们轻松获取一系列关于Spring应用配置内容的详细报告,比如自动化配置的报告、Bean创建的报告、环境属性的报告等。
/conditions
:该端点用来获取应用的自动化配置报告,其中包括所有自动化配置的候选项。同时还列出了每个候选项是否满足自动化配置的各个先决条件。所以,该端点可以帮助我们方便地找到一些自动化配置为什么没有生效的具体原因。该报告内容将自动化配置内容分为以下两部分。
positiveMatches
中返回的是条件匹配成功的自动化配置。negativeMatches
中返回的是条件匹配不成功的自动化配置。beans
:该端点用来获取应用上下文中创建的所有Bean。
/configprops
:该端点用来描述配置属性(包含默认值)如何注入Bean。prefix
属性代表了属性的配置前缀,properties
代表了各个属性的名称和值。所以我们可以通过该报告来看到各个属性的配置路径,比如我们要关闭该端点,就可以通过使用management.endpoint.configprops.enabled=false
来完成设置。
env
:该端点与/configprops不同,它用来获取应用所有可用的环境属性报告。 包括环境变量、JVM属性、应用的配置属性、命令行中的参数。 它不仅返回了应用的配置属性,还返回了系统属性、环境变量等丰富的配置信息,其中还包括了应用还没有使用的配置,所以它可以帮助我们方便地看到当前应用可以加载的配置信息。
/mappings
: 该端点用来返回所有Spring MVC的控制器映射关系报告。
/info
:该端点用来返回一些应用自定义的信息。默认清况下,该端点只会返回一个空的JSON内容。我们可以在application.properties配置文件中通过info前缀来设置一些属性,例如:
info.app.name=spring-boot
info.app.version=v1.0.0
度量指标类:获取应用程序运行过程中用于监控的度量指标,比如内存信息、线程池信息、HTTP请求统计等。上面的应用配置类端点所提供的信息报告在应用启动的时候就已经基本确定了其返回内容,可以说是一个静态报告。而度量指标类端点提供的报告内容则是动态变化的,这些端点提供了应用程序在运行过程中的一些快照信息,比如内存使用情况、HTTP请求统计、外部资源指标等。这些端点对于我们构建微服务架构中的监控系统非常有帮助,由于SpringBoot应用自身实现了这些端点,所以我们可以很方便地利用它们来收集我们想要的信息,以制定出各种自动化策略。
/metrics
:该端点用来返回当前应用的各类重要度量指标,比如内存信息、线程信息、垃圾回收信息等。/metrics端点可以提供应用运行状态的完整度量指标报告,这项功能非常实用,但是对千监控系统中的各项监控功能,它们的监控内容、数据收集频率都有所不同,如果每次都通过全量获取报告的方式来收集,略显粗暴。所以,我们还可以通过/metrics/{name}接口来更细粒度地获取度量信息,比如可以通过访问/metrics/jvm.memory.committed
来获取当前JVM可用内存数量。/health
:它用来获取应用的各类健康指标信息。在spring-boot-starter-actuator模块中自带实现了一些常用资源的健康指标检测器。这些检测器都通过HealthIndicator
接口实现,并且会根据依赖关系的引入实现自动化装配。/threadDump
:该端点用来暴露程序运行中的线程信息。它使用java.lang.management.ThreadMXBean的AllThreads
方法来返回所有含有同步信息的活动线程详情。httpTrace
:该端点用来返回基本的HTTP跟踪信息。默认情况下,跟踪信息的存储采用org.springfrarnework.boot.actuate.trace.InMernoryTraceRepository操作控制类:提供了对应用的关闭等操作类功能。
上面应用配置类、度量指标类里介绍的所有端点都是用来反映应用自身的属性或是运行中的状态,相对于操作控制类端点没有那么敏感,所以它们都是默认启用的。而操作控制类端点拥有更强大的控制能力,如果要使用它们的话,需要通过属性来配置开启操作。在原生端点中,只提供了一个用来关闭应用的端点:/shutdown
(在后续我们引入了Eureka之后,会引入更多控制端点)。可以通过如下配置开启它:management.endpoint.shutdown.enabled=true
在配置了上述属性之后,只需要访问该应用的/shutdown端点就能实现关闭该应用的远程操作。由于开放关闭应用的操作本身是一件非常危险的事,所以真正在线上使用的时候,需要对其加入一定的保护机制,比如定制actuator的端点路径、整合SpringSecurity进行安全校验等。