![微服务从小白到专家:Spring Cloud和Kubernetes实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/981/41202981/b_41202981.jpg)
2.3 Spring Boot的核心功能
在使用Spring Boot开发大型的商业应用之前,我们先通过Spring Boot的HelloWorld程序初步体验Spring Boot的基础特性。
2.3.1 易于使用的依赖管理Starter
在学习Spring Boot使用细节之前,先简单回顾一下Java项目构建方式的历史变迁。最早的Java项目在编译和运行期都需要显式地指定classpath,并以这种方式来管理项目所依赖的jar包和class文件。当时主流的构建工具是ant,但ant只能执行编译和构建相关的任务,没有专门管理项目依赖的工具,这也是后来人们发明依赖管理工具Maven和Gradle的原因(在本书中笔者采用Maven 3.6+)。Maven出现之后,创建Java项目的第一步就是利用Maven Archetype插件创建项目。而Spring Boot在Maven Archetype基础之上更进一步,通过Spring Initializr就可以很方便地创建出一个Spring Boot项目。
要创建Spring Boot项目,需要先了解Spring Boot Starter,Starter提供了一种简单易用的解决方案,帮助Spring Boot新手克服各种主流软件集成时的版本兼容问题。
Starter可以让开发者更方便、快捷地使用某种技术,无需花费大量精力管理依赖关系和版本冲突问题,因为在Starter中,所有依赖项已经被预先定义在pom.xml文件中,开发者只需引用Starter,该技术所需的依赖会自动添加到项目中。
例如在项目中要使用Redis,如果不采用Starter的方式,那么开发者至少要声明两个依赖,一个是spring data Redis,另一个是Redis的客户端链接库,具体代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/064-1.jpg?sign=1739278435-onuKj1sxBAZeXBVvaXMrpj2lhnVQ7IRl-0-54cb9888a8c82712c8efc3796543f883)
在这种模式下,作为开发者,首先需要了解支持Redis的Spring组件是什么,其次要了解该组件依赖了哪些其他组件及其兼容版本,如果依赖的任何组件版本进行了升级,那么所有的依赖关系需要重新测试。
只要使用Spring Boot Starter,以上问题就会迎刃而解。开发者只需引入一个相应的Starter依赖即可,实现代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/064-2.jpg?sign=1739278435-XPICYhXbHj7qYq8aDXGu6eT8qCsXdKDh-0-387e16304348a8a8ecac2501badf9074)
添加上述Starter后,项目引入了哪些依赖,可以在项目的根目录下运行以下命令进行查看:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/065-2.jpg?sign=1739278435-JR8B8tdYnp6RH7hFoRCrqGebT977amLk-0-6fa83983f9551370dd8d67f892914054)
运行结果如图2-4所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/065-3.jpg?sign=1739278435-SWWkdRgXGecqoxpxkpdDpd2NOwNbKm6D-0-7733d5b85de9953b8e11b03c544170cc)
图2-4 Redis Starter的依赖关系
从图2-4可见Redis的Starter主要定义了两个直接依赖spring-data-redis和lettuce-core,spring-data-redis是Spring对Redis操作的封装,lettuce是Redis客户端的Java实现(取代最初的Jedis)。通过这样的方式,所有与Redis相关的lib都会被自动添加到项目中,开发者再也不需要一个个单独地引入依赖关系,执行引入一个Starter即可。当然Starter要真正在运行期发挥功效,是需要使用自动配置技术的(后续在2.3.2节会详述)。Starter还会管理后续的版本升级,如无特殊情况,只需升级Starter的版本便可获得所有依赖组件的升级效果。
为了更好地管理Starter依赖项,建议读者在Spring Boot项目的pom.xml文件中指定继承Spring Boot parent,Spring Boot parent的pom.xml文件中指定了经过兼容测试的Starter组件版本,相关代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/065-4.jpg?sign=1739278435-MC5VZVJenAEenaTxodUrMWBAZd7ywm0w-0-8a4c52552ed992645b59320b6c19ec5a)
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/066-1.jpg?sign=1739278435-eHPLmnEIjkyvllnxJFqLC15a5Qrs268W-0-a7130c040ad0af9d7194a849b65398be)
在实际开发工作中,并不是所有项目都可以继承Spring Boot的parent,比如已经有一个parent且不容易被替换的时候。此时,可以采用另外一种方式——使用引入Spring Boot parent的依赖,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/066-2.jpg?sign=1739278435-dgErfqzV5u7AakMRIQQ9gzeLkPyBVGar-0-bc93a38b5e1a26bd289a7c4c4397ea2e)
如果在dependencyManagement中定义了scope为import的pom依赖项(此处指spring-boot-dependencies),那么该pom.xml文件中定义的所有依赖都可以在当前项目中被直接引用,例如上述例子中引入了org.springframework.boot:spring-boot-dependencies,该pom.xml文件中定义的spring-webmvc版本是5.2.8.RELEASE,在该项目中所有依赖于spring-webmvc的版本默认都是5.2.8.RELEASE。
除依赖管理外,一个符合Spring Boot标准的项目要遵循某些约定(比如配置文件的位置和文件名),典型的Spring Boot项目结构如图2-5所示。
项目根目录下的pom.xml文件定义了该项目的依赖和主体结构,DemoApplication是该项目的运行启动类,application.properties是项目的默认配置文件。
在实际项目中,我们很少会采用人工的方式创建项目或引入依赖项,大多是通过Spring Initializr创建新项目。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/067-1.jpg?sign=1739278435-RhepvTPB8HNLe5gewC8W8zZtReAphlRA-0-2801a970308caebc03680f9e959d5e36)
图2-5 典型的Spring Boot项目结构
要使用Spring Initializr,需要先打开Spring Initializr的官方网站,其界面如图2-6所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/067-2.jpg?sign=1739278435-jbBbz6W78WBI7Uz4gIm57ogT1Lsxe0SD-0-f5aaeeba509443ffff394955a7d14f30)
图2-6 Spring Boot Initializr界面
在Spring Initializr的界面上,执行以下步骤来创建一个Spring Boot项目:
(1)选定项目类型和语言(本书都是Maven和Java)。
(2)选择依赖的Spring Boot版本。
(3)指定项目的group、artifact和打包方式。
(4)单击ADD DEPENDENCIES按钮,添加项目所需依赖。
(5)单击GENERATE按钮,完成项目的创建。
通过这种方式创建项目有三个优点:第一,符合Spring Boot推荐的标准结构;第二,无需操作Maven Archetype插件来生成项目,减少误操作的风险;第三,可以更加直观方便地添加Starter,如图2-7所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/068-1.jpg?sign=1739278435-9zZcG67JlMpJ6Xx8iOtn60DYh56CzCIc-0-dca82f1c091cf26d2a2c78e60928b59d)
图2-7 添加Starter的提示界面
总结一下,Spring Boot Starter是一个具备以下特性的功能性组件:
• 一个针对市面上成熟的规范和开源软件的小型pom项目。
• 一组可以被复用的依赖的最佳实践和默认配置。
• 一种全新的Spring应用开发和依赖管理的方式。
Spring Boot官方提供了很多主流的Starter,官方约定的命名方式是spring-boot-starter-*。Spring Boot允许自定义Starter,用户自定义的Starter的命名方式是thirdpartyproject-spring-boot-starter(将公司或组织的名称放到前面),所有官方提供的Starter都可以通过Spring官方文档查询获得。
2.3.2 约定大于配置的Auto Configuration
我们在2.3.1节中说过,Spring Boot的默认配置功能主要靠Starter定义的默认依赖和Spring Boot的Auto Configuration来实现。
为了更好地理解Auto Configuration,我们先来实现一个简单的演示项目。
下面我们在2.3.1节中创建的Spring Boot项目的pom.xml文件中添加一个新依赖,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/068-2.jpg?sign=1739278435-dcQ5wjPDSvUNNgwVbMrMBaqodDhaZd9J-0-3118700a1db55186a1369866258164f4)
运行该项目(在3.2.2节会详细介绍如何运行Spring Boot项目),输出日志如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/068-3.jpg?sign=1739278435-jugdMWuyBvRS6eA3zOSMtUM2RGFiFRfh-0-149166d32017f7bd20c4a33138f23253)
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/069-1.jpg?sign=1739278435-53SYUXpVelUCEjT6G2wRssov3LbgzztX-0-88cb1f3bc44c47480ea01ff146d876c3)
从上面的启动日志代码可以看出,Tomcat容器被启动并监听8080端口,在项目中并没有添加任何代码或者配置(除了新的pom依赖)。通过这个简单的实验,可以看出Spring Boot根据项目classpath中出现的类和相应的默认配置完成了Bean的装配工作,使整个系统更容易运行,同时减少了配置文件,这就是Auto Configuration的意义。接下来我们介绍Auto Configuration的工作原理。
Spring Boot通过以下步骤实现了Auto Configuration:
(1)在应用启动时,扫描项目classpath中存在的框架类。
(2)检查显式的配置。
(3)结合定义的Condition完成Bean的装配。
Condition是在Spring 4中引入的一种技术,它的设计目的很简单,帮助开发者有选择性地注册Bean,这也是IoC容器的设计目的,应用依赖于接口,而且可以根据不同条件自动切换其实现并被容器注入。
根据Spring Framework的定义,Condition和@Conditional注解配合使用,就可以控制Bean在什么条件下会被加载并初始化。如果一个Bean被@Conditional注解,那么就有可能被注册到Spring Context。当然也有可能不被加载,是否被加载取决于该@Conditional注解中配置的Condition判断条件是否成立。如果判断条件成立(返回true),那么这个Bean就会被注册进Spring Context。
基于以上的基础知识,就不难理解Spring Boot的Auto Configuration功能的原理,Auto Configuration的基本工作原理就是通过各种内置的Condition注解来控制某个Bean是否被加载。从实现层面来看,Spring Boot官方提供了以下几种Conditional组合:
(1)Class Condition
针对class有两个注解@ConditionalOnClass和@ConditionalOnMissingClass,分别表示某个类在classpath中出现或者没有出现。
为了探知这背后的原理,我们将日志级别调整为Debug之后再次启动项目,查看日志中的Class Condition判断结果,将代码logging.level.root=DEBUG加入application.properties,再次启动该项目,启动日志中会出现以下内容:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/070-1.jpg?sign=1739278435-PufScIPB9yIlQkDQzV2thAlKtjI6tk0w-0-5aa49553409b3f93d48ed2fe572dbb31)
EmbeddedWebServerFactoryCustomizerAutoConfiguration类位于spring-boot-autoconfigure-2.*.RELEASE.jar中,其有关Tomcat容器的定义代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/070-2.jpg?sign=1739278435-0FdbukeFtfTs87JmEOBhFhJh8P8ED8Y1-0-654426c50d892a9bacb37241a07bdc00)
以上代码表明,如果Tomcat和UpgradeProtocol两个类出现在classpath中,那么Tomcat容器将会被注册到Spring Context,从而实现了Tomcat的Auto Configuration。
(2)Bean Conditions
Bean类型的condition有@ConditionalOnBean和@ConditionalOnMissingBean,分别表示如果Spring容器中已经存在(或不存在)某个Bean,就注册某个特定Bean到Spring Context。
(3)Property Conditions
针对Property,Spring Boot引入了@ConditionalOnProperty,其基本用法是判断某个property是否出现或某property的值是否等于特定值,从而决定被注释的class是否需要被注册到Spring Context。Property是指Spring Environment Property,在Spring Boot应用中,除了默认的application.properties(或YAML),其他property都需要额外指定,一般都是通过定义PropertySources来指定如何加载property到Spring context。示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/071-2.jpg?sign=1739278435-CTSCG9I0zJsSQ5gUZclDSBPrg64QgE7C-0-ae4d00ad48bbe21918633717330450f0)
(4)Resource Conditions
@ConditionalOnResource的作用是根据某个特定资源是否出现来决定某个类是否要被注册到Spring Context,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/071-3.jpg?sign=1739278435-A9IVGO28HrxJQdu55lZZDSFK0qhe3yg8-0-fc8daee06f179f540b6595014a7e070e)
上面代码的功能是,如果在 classpath 下没有找到 customer.properties 文件,那么在additionalProperties()方法中定义的Properties不会被注册到Spring Context。
(5)Web Application Conditions
与Web相关的Condition有三个:@ConditionalOnWebApplication、@ConditionalOnNot WebApplication和@ConditionalOnWarDeployment,前两个用于判断当前项目是否为Web应用,第三个用于判断项目是否是以war的方式进行部署。
(6)SpEL Expression Conditions
@ConditionalOnExpression是基于SpEL的表达式结果来对Condition的结果进行判断的。我们可以在Spring Bean上定义Condition,但并不是定义了Condition的类都具备Auto Configuration的能力,因为Spring Boot还有另外两种配置来控制Auto Configuration的运行。
用来控制 Auto Configuration 的三个最常用的 Spring Boot 注解是@Configuration、@ComponentScan和@EnableAutoConfiguration,这三个注解在Spring Boot的早期应用中是必备的,而最新版的Spring Boot只需要在启动类上添加一个@SpringBootApplication注解,就会自动开启当前项目的Auto Configuration功能,它的作用等同于@Configuration、@ComponentScan和@EnableAutoConfiguration三个注解共同作用的效果。
我们以2.3.1节中的spring-data-redis依赖项为例,虽然在pom.xml文件中并未定义任何与Redis相关的配置,但在启动过程中仍然可以看到以下日志内容:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/072-1.jpg?sign=1739278435-xMuYlF5hVhDSBnd5JQDtJXNPohLiEcpI-0-2ed07583fd8caf7526fd649035180017)
根据上面日志内容可以判断,Redis相关的Auto Configuration已经生效,我们在代码上做些小修改来验证这种猜想,将SpringBootApplication变成
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/072-2.jpg?sign=1739278435-ThC7uQ2swAPGNL1NMq6IMAzycQtO7r8V-0-070fa4fb5f6bf9449f100d9300b4eb58)
或者
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/072-3.jpg?sign=1739278435-OcvR726NNDDHEduVnruIK6CYPU2GotIx-0-36f6991db4e34fd1722caaab69a87a5f)
在上述代码中,我们通过exclude属性将RedisAutoConfiguration从自动装配过程中剔除,再次启动测试项目,启动日志中不会出现任何与Redis相关的信息,这证明了前面的推测是正确的,即Redis的加载是由Auto Configuration驱动的。
我们进一步分析,如果EnableAcutoConfiguration类可以exclude某些AutoConfiguration类,那么是不是说明Spring Boot和EnableAutoConfiguration需要知道哪些类是AutoConfiguration呢?这些AutoConfiguration类并未实现任何共同的接口或者添加任何特殊注解,Spring Boot是如何判断哪些类需要执行自动配置呢?
检视EnableAutoConfiguration类,其代码(细节有省略)如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/073-1.jpg?sign=1739278435-creSgfRkFZgjaBTLk613XKLQGhnWHXll-0-45b7f8bd4a8045de70ea63121c7136d1)
进一步检视AutoConfigurationImportSelector类,从下面的方法可以找到线索:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/073-2.jpg?sign=1739278435-Vn57nBS48Tp6Y8BpHP4vm2hC6xTNcbmb-0-81457aeed5cec7f9ee633ce95ffb0d85)
META-INF/spring.factories是一个特殊的文件,它存在于spring-boot-autoconfigure-2.*.jar中,借助反编译工具打开上述jar文件,即可看到spring.factories文件,spring.factories文件的部分内容如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/073-3.jpg?sign=1739278435-XfpCNqNL4LfLoOLDUoeRmns7P0SGpHBk-0-76a22676da9d218af69654417a3eedbf)
在上面的代码中,RedisAutoConfiguration赫然在目,据此可以推测,spring.factories文件中定义了被Spring Boot默认加载的AutoConfiguration类。
至此,我们初窥了Spring Boot Auto Configuration的基本工作原理:首先Spring Boot利用Condition针对各种常见的开源框架定义了相应的AutoConfiguration类,然后将这些定义类配置在spring.factories文件中。Spring Boot项目在启动时,会自动按照相应的condition来判断是否需要注册相应的类到Spring Context,最终完成自动配置。
2.3.3 优雅灵活的配置管理Properties
基于Spring Boot的Auto Configuration功能,Spring Boot会根据实际运行环境对应用进行自动配置。在早期的Spring框架中,所有的应用配置都是基于配置文件的,虽然Spring开发了基于注解的配置项管理,但是完全消除配置文件并不现实也非必要。因此,如何更好地管理配置文件就成了一项很有挑战的工作,这一节我们就来探讨如何在Spring Boot项目中实现配置管理。
在开始探索Spring Boot是如何管理配置项和配置文件之前,我们需要先了解Spring的早期版本是如何管理配置的,以此来对比Spring Boot对配置的管理是多么简洁和优雅。
在JDK1.5之前Spring只支持XML形式定义的Bean配置,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/074-1.jpg?sign=1739278435-PE2QKwNgYtSbFEgZP825WnZZ3T3iR7b3-0-9a098a3d3db906cf11200beddbb4ab7c)
在Java支持注释之后,Spring也提供了@Component、@Service、@Bean等注解来定义Bean。
如果在Java Bean中需要加载额外的配置项,而且这些配置项不方便定义在XML文件中,那么一般会采用如下三种方式之一来定义:
(1)@PropertySource注解
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/074-2.jpg?sign=1739278435-seKTS6EgdWWZWPdpDH89aXOvFI8wZdLj-0-22dcaca7149177b20b2c229be6749300)
通过@PropertySource注解指定配置文件的路径,再通过@Value注解加载配置文件中特定名称的配置项。
(2)XML
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/075-2.jpg?sign=1739278435-6YYh2Os0dteemmyeAeqBBEHX6RDuw0on-0-7d4f1222c8fef31d886d81080562f6b0)
我们在XML文件中通过<context:property-placeholder>标签加载配置文件,在上面的代码中,我们加载了classpath下的foo.properties和test.properties两个文件。
(3)PropertySourcesPlaceholderConfigurer
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/075-3.jpg?sign=1739278435-tJfv7TL2EhmMzZ53pc1Vf9yXZfpXBtiQ-0-8fc71b32d8630be8b075c9d9efef9fe2)
在上面的代码中,我们通过PropertySourcesPlaceholderConfigurer类读取了classpath下的指定配置文件。
在引入Spring Boot之前,我们主要通过以上几种方式加载properties。下面,我们通过一个例子来讲解这种传统的配置项加载形式。
假设应用需要访问MySQL,那么我们可以定义一个配置文件,在配置文件中添加如下MySQL的连接信息:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/075-4.jpg?sign=1739278435-wD4EVgxkSsO2K8EKdwqyLsTJYE85GdBP-0-8365dc62251eb9e9f86dcce9b43d4147)
在Java代码中开发者需要通过@PropertySource注解加载这个配置文件,并使用@Value(“spring.datasource.url”)的方式注入所需配置。
虽然这种传统的Properties注入已经实现得很好,但Spring Boot提供了更为巧妙的方式来加载配置项,即使用ConfigurationProperties来定义这种有层级关系的配置,具体代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/076-1.jpg?sign=1739278435-wdZqbs1WiBlH34V4jIfqk2AGxBlGwFKN-0-585776b85cb16a94a2b3d934027551db)
通过@ConfigurationProperties注解,我们可以加载以“spring.datasource”开头的配置项,并将加载后的属性自动设置到MysqlProperties类的字段(如spring.datasource.url将会自动注入MysqlProperties类中的url字段),这样我们就不需要为每个字段单独指定@Value注解。
根据官方文档解释,在Spring Boot 2.2以后的版本中,我们可以省略@Configuration注解,只需要通过@ConfigurationProperties注解就可以打开配置项的加载功能。
在Spring Boot的默认配置下,Spring Boot应用只加载application.properties(或YML文件)配置文件,如果我们想要加载其他配置文件,就需要添加@PropertySource注解,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/076-2.jpg?sign=1739278435-c94ithchGRH9VrxF0d6c4xsQUOWE4yoB-0-76ce6681cd5fc5807d242efd5e5b2c4a)
在上面的代码中,我们加载了classpath下的mysql.properties文件,并将配置文件中以spring.datasource开头的配置项设置到MySqlConfig类的字段中。
Spring Boot会按照一定的优先级顺序尝试从多个路径来加载默认配置文件application.properties(或yml),路径寻址的先后顺序如下:
(1)当前目录的/config及其子目录。
(2)当前目录。
(3)classpath的/config目录。
(4)classpath的根目录。
如果我们遵循默认项目结构(如图2-5所示),将配置文件置于src/main/resources路径之下,那么编译后的配置文件位置(target目录下)如图2-8所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/077-1.jpg?sign=1739278435-aS5ud74yuIVBibmLPyJuINJbXtiMqx3r-0-0832ed765419ed20ff5fbf60101aaac1)
图2-8 编译后的目录结构
如图2-8所示,在resouces目录下的application.properties文件在编译后被放置于target/classes目录下。
除此之外,配置文件的加载顺序还将遵循以下原则:
• 当前路径并非指向代码所在路径,而是指向程序运行时启动Java进程的路径。
• 高优先级并不意味着应用在找到文件后就停止查找,而是会查找以上所有路径,如果application.properties文件出现在多个路径下,那么对于相同key的property,高优先级的会覆盖低优先级的,没有冲突的key则会被统一加载到Environment中。
• 如果同时存在YML配置文件和application.properties配置文件,那么YML文件的优先级高于application.properties文件。
• 如果因为某些原因希望改变Spring Boot的默认行为,可以通过两个参数来调整:spring.config.name(用于指定默认的配置文件名,默认值为application.properties)和spring.config.location(用于指定查找配置文件的路径,替代默认的四个寻址路径),这两个参数可以结合使用。
在显式指定application.properties文件的路径后,Spring Boot会按照以下顺序来寻找并注入property:
(1)开发者工具Devtools全局配置参数。
(2)单元测试上的@TestPropertySource 注解指定的参数。
(3)单元测试上的@SpringBootTest注解指定的参数。
(4)命令行指定的参数,如Java -jar springboot.jar --name=test。
(5)命令行中的spring.application.json指定的参数,如Java-Dspring.application.json="{'name': 'test'}"。
(6)ServletConfig初始化参数。
(7)ServletContext初始化参数。
(8)JNDI参数,如Java:comp/env/spring.application.json。
(9)Java系统参数,如System.getProperties()。
(10)操作系统环境变量参数。
(11)RandomValuePropertySource随机数,仅匹配ramdom.*。
(12)JAR包外面的配置文件参数application-{profile}.properties(YAML)。
(13)JAR包里面的配置文件参数application-{profile}.properties(YAML)。
(14)JAR包外面的配置文件参数application.properties(YAML)。
(15)JAR包里面的配置文件参数application.properties(YAML)。
(16)@Configuration配置文件上 @PropertySource注解加载的参数。
(17)默认参数,通过SpringApplication.setDefaultProperties来指定。
以上顺序是序号越小优先级越高,即开发者如果在一个低优先级的地方(序号大)定义了一个property的值,然后又在一个高优先级的地方(序号小)定义了相同名称的property,那么应用中最终注入的是高优先级定义的值。
Spring Boot的配置除具有以上功能外,还具有profile功能,但profile功能并非Spring Boot特有的,它是Spring Framework的功能,并不与Spring Boot绑定。为什么需要profile功能呢?
举一个很简单的例子。在工业级应用中,需要搭建开发环境和生产环境,应用在不同环境下需要使用不同的配置(比如在开发和生产环境中配置不同的数据库连接串),但开发者并不希望引入额外的代码逻辑来控制连接的数据库,此时Spring profile就派上了用场。在Spring Boot的场景下,开发者可以添加两个不同的配置文件appplication-dev.yml和application-prod.yml(profile属性对应文件名中的dev和pord),在启动应用时可以通过启动参数指定profile加载不同的文件,示例参数如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/079-1.jpg?sign=1739278435-ZM3dr5G5tuuUWAj4VnsJRlVRFXsJjP12-0-35485eb50feb353104a4138f0975ea22)
这样就可以实现相同代码运行在不同环境的目的。仍然以MySQL配置为例,示例如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/079-2.jpg?sign=1739278435-JptxZXVVOP6We2xOQobKtzasQhzWaH9e-0-7d35405da9879edd12335508dd75a340)
配置类不需要做任何修改,只需定义两个不同profile的配置文件:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/079-3.jpg?sign=1739278435-gwBuq2aeXzpwqV4pT9MoeInfHpaZCFb6-0-3f0ef0fde3ca8eadae12c166a574c3d1)
这样,相同的代码通过profile加载对应的配置文件,既无需修改代码,又实现了环境的无缝切换。
当开发者需要注入某配置而生产环境并不需要该配置时,可以通过添加@Profile来对配置做出限定,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/079-4.jpg?sign=1739278435-Sos5bcECj6ihZokt01bQ2DBrNzMr5GrC-0-fbebe10baf4567294dc0bdf35ac86115)
@Profile也支持否定@Profile("!dev"),这说明在非dev环境下该配置才能被激活和装载。
Spring Boot的配置管理功能是一件强有力的武器,掌握相关的基础知识是理解并熟练运用Spring Boot的坚实基础,建议读者根据本章的思路对每个功能点都做一个测试案例。
2.3.4 简单明了的管理工具Actuator
假设一个Spring Boot应用开发完毕并部署到生产环境后,刚好应用出现了线上问题,而运维团队希望开发团队能提供一些工具帮助定位问题,作为开发者有什么工具可以提供呢?
再假设应用部署成功,但是通过观察发现应用运行的方式与预期不一致,通过查看日志,发现加载的Bean自动配置有误,由于程序在生产环境中运行,所以无法直接调试,那么用什么方法来解决这个问题呢?
作为Java开发者,对JMX都不陌生,我们可以通过JMX监控系统的运行状态(或调用应用服务),但是如何访问JMX的MBean却是一个问题,因为JDK自带的JConsole默认都是关闭的,因此有很多应用服务器都会额外提供基于Web的JMX console,例如JBoss,那么Spring Boot作为自带内置容器的应用,是如何暴露和访问JMX MBean的呢?
假如应用都是无状态服务,并通过负载均衡器对外暴露接口,而负载均衡器需要通过一个状态检查的接口来判断应用是否运行正常,那么每个类似的应用是否都需要开发一个简单的API来做健康检查呢?
以上种种都是开发者常见的需求,而Spring Boot作为应用框架提供了Actuator来支持这类需求。
要使用Actuator,首先要添加Actuator依赖到项目的pom.xml文件中,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/080-1.jpg?sign=1739278435-B0WURXO4JJZa1lYZt2GxnrCh142gMNHc-0-0901a4458def30b4c3a8bd1d62fef465)
然后,启动应用,启动日志中会增添以下内容:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/080-2.jpg?sign=1739278435-5c5qYInDF7W1zTlE4FNgnZJPEdg4rngs-0-5d9c289d8f0bee4b150cca8a59c456d1)
从上面代码中可以看到,Spring Boot容器多暴露了一个endpoint: actuator,此时在浏览器中输入http://localhost:8080/actuator会得到如下响应内容:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/080-3.jpg?sign=1739278435-Ld7drkpITjIkd3ppdjRJkseY5JIfoJLO-0-c615e206c20d2dbb4d43923cff73cec3)
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/081-1.jpg?sign=1739278435-NwYXQexGkJ8TdKLVBwj9vYFZ5W5QkdBL-0-305180f6bf2385cdf1234d80eb7bc1f5)
该响应类似于REST接口的返回结果,返回了一组新的endpoint,此时我们再访问其中一个endpoint,在浏览器中输入http://localhost:8080/actuator/health,响应内容如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/081-2.jpg?sign=1739278435-WfHih8K1VMbb26lpeUgzg9yj6Kqp1iGR-0-fca3a8bfd0f0c37eb4a571be04604ea6)
这表示当前Spring容器运行正常,因此Actuator是一个检查应用运行情况的轻量级工具。
上面的示例代码演示了Actuator的一个基本功能,Actuator所提供的全部功能清单如表2-1所示。默认情况下,用户可以通过http://host:port/actuator/{endpoint}来访问Actuator提供的各种功能。
表2-1 Actuator功能清单
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/081-3.jpg?sign=1739278435-iU237kCALHALiV8O2MyyRm6FUzymoWkU-0-0adbc692b829f6e3fb2e55d53c2a72dd)
以上这些功能在默认情况下除shutdown外都是开放的,控制功能是否开放可以通过下面的配置代码来实现:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/082-2.jpg?sign=1739278435-iElQtfYDHTbQvx5LC2FT2zel8TlYpZpZ-0-06d2810ef0e3258f5521640a52e987e0)
也可以通过下面的配置代码来实现:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/082-3.jpg?sign=1739278435-JiIn0TMB8HNpSGGrWDTm64OZl1uMXzrR-0-91f12e0ae7156e649868560ad8f24fce)
Actuator默认情况下处于开放状态。既然默认都是开放的(除shutdown外),为什么访问http://localhost:8080/actuator/metrics时却得到404错误呢?原因是,在默认情况下这些endpoint都是通过JMX暴露的,如果通过Web方式直接访问,则需要添加以下代码:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/082-4.jpg?sign=1739278435-fzTkdgP44TnrsRBDxW2oMbP6lV6iw1RN-0-40b283992fb800e8fd07a8a249f65db9)
此时访问http://localhost:8080/actuator/metrics将会出现以下结果:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/082-5.jpg?sign=1739278435-39brTQ8Ivc9JFNybRGm7kMv6fBHJ7Jj5-0-4daaad584b6eaec47ba01d7f2e1e26e8)
综上所述,开发者可以通过以上配置代码来控制endpoint的开放及开发的方式。
为了便于演示,将所有endpoint以Web形式暴露,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/083-1.jpg?sign=1739278435-B7yZoNu00Sio1hxYeffdBaLF8mGv8Sdp-0-e83bd887262747e2cf1c3b29a0f60383)
再访问http://localhost:8080/actuator,页面响应内容如图2-9所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/083-2.jpg?sign=1739278435-yd0kG9rdiW6pklwBqC2ZS51yhEtcJoyF-0-18d20e2b15a7feb3cb00a168207c1a60)
图2-9 修改后的Actuator暴露的功能
下面我们以actuator/beans和actuator/health为例,描述endpoint的功能。
• actuator/beans
主要功能是展示Spring容器中所有存活的Bean,访问此endpoint的返回内容如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/083-3.jpg?sign=1739278435-62Ucu98ytsl8waYIy3DIpbDKJ4ZAPmK8-0-9ab7028bc6bc7049bd479933388366ca)
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/084-1.jpg?sign=1739278435-zI5ulk3jVRPUEH1Ikw8TEc6zHLcF35N7-0-d0c4d535caed9c939da6177e68893a1f)
• actuator/health
主要功能是通过返回一些基础信息来表明应用是否正常工作,但默认配置的health功能所提供的信息非常有限,如果需要health功能提供更多信息,则需要添加以下代码:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/084-2.jpg?sign=1739278435-Khr1BYnmFFhZL7ME1VcozWYrC3lKY0Yr-0-a208751e8c09e8c2fef04289b4d460ca)
重启应用后再访问该endpoint,将会返回额外信息,内容如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/084-3.jpg?sign=1739278435-748dAC7DWBhZ82zghdnRuVtP5TjDwGPp-0-7d103db372d81280f2737b8321f517fd)
此外,还有几个常用的endpoint,名称及其功能介绍如下:
• actuator/configprops
主要功能是展示应用中使用ConfigurationProperties定义的配置。
• actuator/conditions
主要功能是展示应用中所有Auto Configuration的配置。
• actuator/env
主要功能是展示应用中所有的property,包括激活的profile的property、系统环境变量和应用中自定义的property。
• actuator/mappings
主要功能是展示当前应用中所有@RequestMapping定义的映射关系,这对查找Web应用的URL十分有用。
Spring Boot支持丰富多样的endpoint,限于篇幅原因,本章不再一一列举,建议读者通过Spring Boot官方文档进一步了解endpoint的详细功能。
除此之外,Spring Boot Actuator自带一些常见的第三方组件的检查,可以通过额外的配置将其与endpoint进行集成,例如MySQL、MQ和Redis等。下面以Redis为例添加Redis Starter依赖,具体代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/085-1.jpg?sign=1739278435-Dj0A5fcHu1mthKnEiR4BsM1ZoMxHW3AH-0-74cdf7bbef2f9466dd508628f8d4b741)
再添加Redis的配置类,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/085-2.jpg?sign=1739278435-tIerRju8v0wuCw7BdhvOH8cTBe6l2qyJ-0-f1d9f231c7ecadd4e67b88bfb2e6731d)
完成修改配置之后,再次访问endpoint(actuator/health),在返回的响应内容中会有Redis的健康状态,其相关内容如下(常规的health信息省略):
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/086-1.jpg?sign=1739278435-CaezTjiSUpIClRnwAVYkS0lND1kBVsVm-0-87173568dd3cc12f0427867671ac3a82)
在测试时,如果应用没有成功连接到Redis服务,则显示的Redis状态将会是DOWN;如果连接成功,则状态将会显示为UP。
除了Spring Boot自带的这些检查功能,开发者还可以通过实现HealthIndicator为系统添加额外的健康检查功能。此外,开发者还可以按需定制Actuator的URL及访问权限,这些内容可以到Spring Boot官网查看。
2.3.5 方便快捷的内置容器Embedded Container
笔者在2.1.2节中提到,相对于传统的Spring应用,Spring Boot还有一项不同,即Spring Boot项目不需要额外的应用服务器也可以运行。这是因为Spring Boot项目自带内置容器,那么内置容器与传统应用服务器有哪些区别呢?Spring Boot自带哪些内置容器?作为开发者,应当基于什么标准选择容器呢?
在本书中提到的应用容器默认都是指Servlet容器,根据Spring Boot 2.*版本的不同,内置容器所支持的Servlet版本也不同,具体版本如表2-2所示。
表2-2 Spring Boot容器与Servlet版本的对应关系
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/086-2.jpg?sign=1739278435-O7jjtciQqatk5YVDo1exYGpOLXe9SLVe-0-ce0464f57e0c730fc4622cbf5b3e33be)
由表2-2可知,Spring Boot 2.*支持的Servlet最低版本是Servlet 3.1,即如果要部署Spring Boot 2.*版本的应用到单独的应用服务器上,那么该服务器所支持的Servlet规范版本必须是3.1版本以上。
在Spring Boot应用中,无需添加任何特殊配置,只需添加Web相关的Starter,就可以将项目转化为Web应用。在启动该应用时,会默认将该应用运行于内置的Tomcat容器中,相较于传统的开发方式,省却了部署的过程,提高了整体效率。那么,该过程是如何实现的呢?
根据前面所了解的Spring Boot特征,首先要检查Starter依赖,其次要检查Auto Configuration的默认配置,代码如下。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/087-1.jpg?sign=1739278435-b7JeDWtjzFjgc2s3lQnG1UysErbF5PwJ-0-5c580424b1cd0963274fd6fba3fd8aeb)
自动运行Tomcat主要是通过Tomcat Starter来实现的,查看spring-boot-starter-tomcat定义的依赖的代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/087-2.jpg?sign=1739278435-99QiTqmMU37tge8Udd950SVt1L5wQ59o-0-fa7dd85c62aa85c7946250837fbc4c7a)
因此,Spring Boot的内置容器是基于Apache Tomcat的内置版本实现的。使用Tomcat内置版本,应用无需打包成war,以jar文件的形式就能运行该应用,也正是这种可以单独运行而无需额外应用服务器的部署方式,让传统的Web应用更容易被微服务化和容器化,同时这也是Spring Boot吸引众多开发者的原因。Tomcat为什么是默认的内置容器,对此我们在2.3节已经基于Auto Configuration进行了分析研究,本章不再赘述。
Spring Boot应用为了更好地支持容器化(Docker类容器而非Servlet容器)和微服务化,大多将应用运行于内置应用服务器中,而开发者只需添加Web Starter即可获得以上便利。
虽然Tomcat是默认的内置应用服务器,但切换到其他内置服务器也是可以的。以Jetty为例,我们先将Tomcat从Web Starter中剔除,再添加Jetty相关的依赖,实现代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/087-3.jpg?sign=1739278435-JjwQSACMZYHw2gtO2Kbi9w2ge2lHo0CQ-0-1844921871c0c54f8588ab2de5e6f2ad)
然后,启动应用,启动日志如图2-10所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/088-2.jpg?sign=1739278435-izWGVLbh81DXqJpw1d3C0O9yhxBajUpH-0-f43972c3c12738b0407ca7d09fc2b388)
图2-10 Jetty的启动日志
从启动日志可以看出完全没有Tomcat的信息,取而代之的是Jetty的信息。
虽然大部分Spring Boot应用都选择运行在内置Tomcat之内,但也有将Spring Boot部署在独立Apache Tomcat容器的需求,具体的部署过程如下。
首先,从官方网站下载与Spring Boot内置容器版本相同的Apache Tomcat安装文件,解压缩Apache Tomcat,为后续操作做准备(对于使用Linux/Mac操作系统的读者,需要留意Tomcat安装路径下bin目录的文件操作权限,如果在启动日志中看到由于文件操作权限导致的异常,则需要使用命令行工具修改bin目录的文件操作权限)。
然后,对代码进行修改,因为演示项目由Spring Initializr生成,产生的pom.xml文件均默认采用内置Tomcat运行模式,所以需要先改造pom.xml文件。
默认的Spring Boot项目都以jar的形式运行,在pom.xml文件中定义如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/089-1.jpg?sign=1739278435-8g97FpeUdzVEV6rLK6D14DijHuWUBqI6-0-f1b4905b6a6500919bcf9d917ba2d16f)
通过修改package配置,将打包输出由jar改为war,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/089-2.jpg?sign=1739278435-OJqy51lSlAI3eUU6ztAI4ES8OeIYqEFj-0-05e3247af677a7d9e653dda0bb9c1ccf)
接着,将默认的内置Tomcat从依赖关系中进行exclude,为了编译成功,需要将Servlet规范添加为项目的依赖,但由于Apache Tomcat会在运行期提供Servlet规范的类,所以将此依赖的scope设置为provided,示例代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/089-3.jpg?sign=1739278435-2y1sk0XnAp43sYFcBqyvbmE2BYBkToX7-0-5c7f536767c36b35af8d0311b00abc2d)
最后,在<build>区域,由Spring Initializr生成的pom.xml文件会自动添加Spring Boot的Maven Plugin,以此方式编译出可执行的jar文件。由于不再使用jar运行,而是使用war并部署到Tomcat,所以build也需要做出相应调整,具体代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/089-4.jpg?sign=1739278435-y2Vt2Qh5GfwlvqiKNKiL96arBGKEotTE-0-986e9a4c233ada674f1ca228b8586c46)
下面,我们添加测试代码来验证部署效果,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/090-1.jpg?sign=1739278435-ci1qKfSxjhSV4wyt4W1ho8T5EZ98tHCX-0-04cc5157b797e9e66d16d5b66e989f0a)
此外,DemoApplication也要进行修改,以适应独立的Tomcat部署,代码如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/090-2.jpg?sign=1739278435-BX2CEZUnRyj14vsseFtn4qT9Csa9d0V2-0-adff290c9de3c24c7a041a25a7a41999)
与普通的Spring Boot应用启动类不同,如果项目采用容器部署的方式,那么该项目的启动类需要继承SpringBootServletInitializer,再编译并部署,具体步骤如下:
(1)在demo项目根目录下运行mvn clean package。
(2)复制demo项目根目录下target目录下的demo.war(此文件名有<build>下的fileName决定)文件到Tomcat解压缩后的根目录下的webapps目录上。
(3)进入Tomcat根目录下的conf目录,修改tomcat-users.xml文件,添加内容如下:
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/090-3.jpg?sign=1739278435-28VwLVNqSED7xFKgj6QlfE5MwBKlWonn-0-576eb70deab361d90f30e287c68490ae)
(4)进入Tomcat根目录下的bin目录运行start(Windows start.bat、Linux平台和macOS则运行start.sh)。
(5)观察Tomcat根目录下的log目录catalina.out,看看该目录下是否有启动成功的日志。
(6)打开Tomcat主界面(http://localhost:8080),如图2-11所示。
单击Manager App,在登录框输入新建的用户名/密码(tomcat/password)就会进入管理界面,如图2-12所示。
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/091-1.jpg?sign=1739278435-kpHI1hOjIHrkeFZsYAP4DG6PVT6qE6Kl-0-91c05873d73d9decf1d2a7e188180662)
图2-11 Tomcat主界面
![](https://epubservercos.yuewen.com/91A00C/21440188001525406/epubprivate/OEBPS/Images/091-2.jpg?sign=1739278435-0e6hKO96dsnnNREsLPHPoWLAqp2LPf1f-0-7919c13438abde7fba6a77f32cd37455)
图2-12 Tomcat管理界面
从图2-12可以看出demo应用已经部署成功,在浏览器中输入http://localhost:8080/demo/hello,即可访问我们在TestController中添加的sayHello()方法。
根据项目的不同需求,开发者可以自行选择不同的部署方式,要想了解内置服务器的更多配置,请参考Spring Boot官方文档。