《SpringBoot 3:入门与应用实战》第 5 章 使用 Spring Boot 阅读笔记 9
通过第一部分的学习,相信读者已经对 Spring Framework 的基础有了足够的了解。在实际的项目开发中,使用原生 Spring Framework 的工程很容易出现以下问题。
- 工程搭建起来比较烦琐,需要自行梳理工程中的依赖关系和依赖版本。
- 项目中充斥着大量的 XML 配置文件、注解配置类等多种配置源,并分散在工程的不同位置。
针对这些问题,Spring 的官方团队在历经波折后,于 2014 年正式推出 Spring Boot,这意味着基于 Spring Framework 的 Web 应用开发进入了一个新的模式:开发者不必再去纠结烦琐的配置等问题,而是专注于业务的开发。就其本身而言,Spring Boot 不是一个全新的框架,而是基于Spring Framework 的"二次封装",所以一切底层都基于原生的 Spring Framework。
5.1 Spring Boot 概述
最初的 Spring Boot 要追溯到 2013 年,当时 Spring Framework 开发团队就在着手准备 Spring Boot 项目的开发,他们的想法是打造一个"支持不依赖外部容器的 Web 应用程序体系结构"。2014 年 3 月 28 日,Spring Boot 1.0.0 版本正式对外发布。时至今日,Spring Boot 已经迭代到 3.x 版本,Spring Boot 的社区活跃度极高,这充分证明了 Spring Boot 作为当下最主流开发框架的地位。
可以这样理解,Spring Boot 是开发者与Spring Framework 之间的一道中间层,它帮助开发者完成部分基于 Spring Framework 的项目配置、管理、部署等工作,目的是为开发者"减负",让开发者关心项目中的业务开发,而不是把一部分精力浪费在项目环境搭建和琐碎的配置上。简单地说,Spring Boot 可以轻松创建独立的、容易引入第三方技术的、生产级别的应用。
5.1.1 Spring Boot 的核心特性
Spring Boot 设计之初的目的是,简化基于 Spring Framework 的项目搭建和应用开发,而不是代替 Spring Framework,所以 Spring Boot 提供了以下几个核心特性来帮助开发者简化配置,以及构建企业级应用。
- 约定大于配置:Spring Boot 对日常开发中比较常见的场景都约定了默认配置,并基于自动装配机制,将场景中通常必需的组件都注册好,以此来实现少配置甚至不配置就能正常启动项目的效果。
- 场景启动器:Spring Boot 对常用的场景都进行了整合,将这些场景中所需的依赖都收集并整理到一个依赖中,并在其中添加了默认的配置,使项目开发中只需导入一个依赖,即可实现技术场景的整合。
- 自动装配:Spring Boot 基于 Spring Framework 的模块装配 + 条件装配,可以在具体的场景下,自动引入所需的配置类并解析执行,并且可以根据项目代码中已经配置的内容,动态注册组件,以实现约定大于配置的效果。
- 嵌入式 Web 容器:Spring Boot 在运行时可以不依赖外部的 Web 容器,而是使用内部嵌入式的 Web 容器来支撑应用的运行,也正因如此,基于 Spring Boot 的应用可以直接以 jar 包的形式运行。
- 生产级别的特性:Spring Boot 提供了生产运维型的功能特性,比如健康检查、监控指标、外部化配置等。
5.1.2 Spring Boot 的体系
Spring Boot 支持的常见场景。
- Spring WebMvc 和 Spring WebFlux--- Web 应用开发。
- Thymeleaf 和 Freemarker--- Web 视图渲染。
- Spring Security --- 安全控制。
- Spring Data Access --- 数据访问( SQL 和 NoSQL )。
- Spring Cache --- 缓存实现。
- Spring Message --- 消息中间件( JMS 和 AMQP )。
- Spring Quartz --- 定时任务。
- Spring Distribution Transaction --- 分布式事务( JTA )。
- Spring Session --- 分布式 Session。
- Container Images --- 容器镜像构建支持。
可以发现 Spring Boot 可以整合的技术场景非常多,项目需要用到特定的场景或技术时,只需导入对应的启动器依赖,之后编写少量配置甚至不编写配置,就可以实现场景整合的效果。另外基于场景启动器的整合不需要开发者考虑版本问题,因为 Spring Boot 早已帮助开发者考虑并适配好。
本书使用 Spring WebMvc 指代基于 Servlet 的 Web 开发,读者可能对 Spring WebMvc 的更熟悉叫法是 Spring MVC,这两者指代的是同一项技术。为了更好、更清楚地区分 WebMvc 与 WebFlux,本书后续提到的所有 Spring MVC 简称为 WebMvc。
5.2 Spring Boot 快速使用
了解 Spring Boot 的理论基础知识后,下面通过一个简单工程体会 Spring Boot 的项目构建和快速开发。
5.2.1 创建项目
创建基于 Spring Boot 的项目方式有两种常用的方式。
1.基于 Spring Initializr


表单中的可选项较多,简单说明如下。
- Type:项目构建工具,可选 Maven 或 Gradle。
- Language:项目编程所使用的语言,可选 Java、Kotlin、Groovy。
- Spring Boot:选择所使用 Spring Boot 的版本(注意此处只能选择最新 RELEASE 版和紧跟着要"转正"的 SNAPSHOT 版)。
- Project Metadata:项目的基本信息,包含 Group、Artifact 以及 Spring Boot 启动类所在的包、打包方式、Java 语言版本等。
- Dependencies:项目所使用的依赖,在这个位置可以搜索并选择 Spring Boot 预先准备好的一些场景启动器。在本节的演示中我们只选择 Web目录下的 "Spring Web" 即可。
2.使用 Maven/Gradle 手动创建
使用 Spring Initializr 的方式,本质是使用固定代码模板生成的 Maven/Gradle 项目。
在 IDEA 中创建一个 Module 模块。pom.xml 文件中需要继承父工程 spring-boot-starter-parent(所有基于 Spring Boot 的工程都必须继承该父工程),声明 Java 的版本,并添加 spring-boot-starter-web 依赖和 spring-boot-maven-plugin 插件,即可完成基于 WebMvc 的场景整合。
随后在工程的 src/main/java 目录下创建一个 com.yangjunbo.quickstart 包,并在其中创建一个 Spring Boot 的主启动类SpringBootQuickstartApplication。主启动类的编写方式是固定的,在类上标注 @SpringBootApplication 注解,并声明 main 方法使用SpringApplication 驱动运行。如此编写完毕就可以运行 main 方法启动工程。
3.启动工程
当实际运行main方法后,控制台会打印很多信息。

- 一个很大的 Spring 线条图,我们称之为 "Banner"。
- Tomcat initialized with port 8080 (http):工程中附带了一个 Tomcat,并将在 8080 端口初始化。
- Starting Servlet engine: Apache Tomcat/11.0.24:内置的Tomcat版本为10.1.8。
- Root WebApplicationContext: initialization completed in 559 ms:内部有一个嵌入式的 WebApplicationContext,它是 ApplicationContext 的子实现。
- Tomcat started on port 8080 (http) with context path '/':Tomcat 成功运行在 8080 端口,当前工程的 context-path 为 /。
- Started SpringbootQuickstartAApplication in 1.304 seconds (process running for 1.962):程序运行共耗时 1.304 秒。
5.2.2 快速编写接口
Spring MVC 底层基于 Servlet 开发,下面编写一个类似于 Servlet 的组件,体会一下 Spring Boot 的开发之迅速。
新建一个 controller 包,并在其中创建 HelloController 类,定义一个 hello 方法用于接收请求并响应。

简单解释这段代码的含义。
- @RestController:模式注解 @Controller 的派生注解,被 @RestController 标注后,当前类中所有标注了 @RequestMapping(或派生注解)的方法会将方法的返回值响应到客户端浏览器,如果返回值是对象,还会被序列化为 JSON 格式的数据。
- @GetMapping("/hello"):当前方法是一个支持 GET 方式请求的 HTTP 接口,请求路径为 /hello。
编写完成后,运行预期是当项目启动后访问 /hello 请求,可以响应 "hello SpringBoot" 的字符串内容。
再次运行 SpringBootQuickstartApplication 主启动类,访问 http://localhost:8080/hello,可以看到浏览器成功接收到响应 "hello Spring Boot",这意味着基于 Spring MVC 的接口开发完成。

5.2.3 打包运行
Spring Boot 的强大特性之一是 "直接运行"。可以将当前的工程打成一个 jar 包。找到当前工程的 pom.xml 文件,在文件所属目录下使用命令行执行 mvn clean package 命令,可以在生成的 target 目录下找到一个 jar 包。使用命令行执行 java -jar xxx.jar,可以发现这个 jar 包已被成功启动,并且内置的 Tomcat 也成功运行在 8080 端口。



再次访问 http://localhost:8080/hello,依然可以获得正确的响应,这就印证了 "直接运行" 的概念:Spring Boot 支持使用嵌入式 Web 容器,将工程制作成独立可运行的 jar 包,通过运行 jar 包就可以直接启动工程。

支撑 Spring Boot 工程打包直接运行的依赖是 pom.xml 文件中的 spring-boot-maven-plugin 插件,如果将该插件去掉,则打包后无法通过 java -jar 命令启动。

5.2.4 修改配置
Spring Boot 中默认导入的 Tomcat 会在 8080 端口上运行。如果我们需要修改运行的端口号,对于传统的 Tomcat 需要找到 server.xml 文件并修改配置,而 Spring Boot 中只需要修改 application.properties 或 application.yml 文件中的内容。

修改之后,重新运行主启动类,在控制台中可以发现 Tomcat 的运行端口已经改变,此时再访问http://localhost:8080/hello 就会失效,需要改变访问路径为 http://localhost:8888/hello 才能生效。



5.3 Spring Boot 的依赖管理
通过 5.2 节的快速使用,可以感受到 Spring Boot 的功能之强大和使用之便利,而这背后的强大支撑源自两大特性,分别是本节要讲解的依赖管理和 5.4 节的自动装配。在刚开始搭建基于 Spring Boot 的应用时,pom.xml 文件中只导入了一个 spring-boot-starter-web 依赖就完成了整个WebMvc 场景的开发。如果观察 IDE 中的工程依赖,可以发现该模块中导入了包括 Spring 系列的 jar 包、与 JSON 处理相关的 Jackson 工具、Tomcat 服务器等依赖,而我们实际没有自己编写这些依赖,甚至连它们的版本号都没有声明,这背后的机制就源自 Spring Boot 强大的依赖管理机制。

5.3.1 场景启动器
Spring Boot 官方提供了一组事先制作好的 pom 依赖,这种依赖有一个专有名词叫 "场景启动器",它们都以相同的命名规范定义,所有 Spring Boot 内置的依赖都以 "spring-boot-starter" 开头,不同的后缀代表不同的依赖坐标,分别指代不同场景下的启动器,如 spring-boot- starter-web代表基于 Servlet 的 WebMvc 开发场景启动器,spring-boot-starter-aop 代表启用 AOP 特性的开发场景启动器,spring-boot-starter-jdbc 代表原生 JDBC 支持的开发场景启动器等。关于详细的依赖坐标列表,读者可以参考 GitHub 中 Spring Boot 工程的 spring-boot-projects/spring-boot-starters 子工程,或者直接从 Maven 的中央仓库中查看 groupId 为 "org.springframework.boot" 的发行版依赖即可。
此外,针对 Spring Boot 官方没有整合的技术,许多第三方框架也推出了自己整合 Spring Boot 的方案,此称之为"第三方场景启动器",比如MyBatis 中推出的 mybatis-spring- boot-starter。
具体来看,以5.2节的 springboot-01-quickstart 工程为例,我们导入的 spring- boot-starter-web 依赖中包含一系列依赖,其中包含 WebMvc 系列的依赖、Tomcat 的依赖、Jackson 的依赖。

由于我们使用 Maven 来构建项目,而 Maven 有一个依赖传递的机制(假设 A 依赖 B,而 B 又依赖 C,则 A 无须显式声明依赖 C,借助 B 即可拥有对 C 的依赖),通过这个机制就可以把一个场景中的所有依赖全部导入,即引入一个依赖坐标,底层由 Maven 帮我们导入了非常多的依赖。
5.3.2 版本管理
在编写上述示例时,根本没有考虑导入依赖的版本问题!在第 2~4 章中,我们导入的所有依赖都有明确的版本号声明,而在 5.2 节中,我们导入的 spring-boot- starter-web 依赖并没有标注版本号,而这一切是由于 pom.xml 文件中给当前工程指定了一个父工程 spring-boot-starter-parent,这里面暗藏玄机。
借助 IDE 打开 spring-boot-starter-parent 的 pom.xml 文件,可以发现它继承了另一个父工程 spring-boot-dependencies,而在这个父工程中,我们可以从 标签中发现问题的奥秘:对于 Spring Boot 每一个版本中内部整合的所有技术,在 spring-boot-dependencies 中都已经声明了对应的版本;另外,在 标签的下方还有一个 标签,其中统一定义了所有 Spring Boot 内置支持的依赖信息和版本,这样做的效果是我们在搭建工程引入依赖时,只需要引入场景启动器,而无须关心内部整合了哪些组件和版本适配。




5.4 Spring Boot 的自动装配
Spring Boot 之所以能帮我们快速地搭建开发环境,内部设计了两大核心特性,依赖管理和自动装配 。
依赖管理,统一定义了所有 Spring Boot 内置支持的依赖信息和版本。
自动装配,当导入一个场景启动器时,Spring Boot 会默认向 IOC 容器中注册非常多的组件。
5.4.1 组件自动装配
如果在阅读本书之前了解了有关传统的 SSM 甚至 SSH 框架的整合,应该了解整合的内容中包含很多组件配置。关于这些组件的注册,在我们搭建基于 Spring Boot 的工程时都没有见到过,这是否意味着工程中没有注册组件?为了验证组件是否存在,我们可以尝试获取 IOC 容器并打印其中所有 Bean 的名称。SpringApplication 类的 run 方法会返回一个 ConfigurableApplicationContext,关于这个接口在第 4 章中已经讲解过,它是 "可写" 的 IOC 容器模型,它拥有一个 getBeanDefinitionNames 方法,通过该方法可以获取 IOC 容器中所有 Bean 的名称。

java
package com.yangjunbo.springboot.quickstart;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.Arrays;
@SpringBootApplication
public class SpringbootQuickstartApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(SpringbootQuickstartApplication. class, args);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
执行 main 方法启动工程,观察控制台中的输出,可以发现有一个 characterEncodingFilter,同时还打印了诸如 dispatcherServlet、mvcViewResolver 等组件,说明默认情况下当我们导入一个场景启动器时,Spring Boot 会默认帮我们向 IOC 容器中注册非常多的组件。
5.4.2 默认组件扫描
上面编写的工程中定义了一个 HelloController,这个类放在 com.yangjunbo.springboot.quickstart.controller 包中,而 Spring Boot 的主启动类放在父包 com.yangjunbo.springboot.quickstart 中。如此编写之后 HelloController 也在工程启动后装载到了 IOC 容器中,这就意味着 Spring Boot有一个默认的组件扫描规则:扫描主启动类所在包及子包下的所有组件。
为了验证这一机制,我们可以将 HelloController 移至其他包中,此时如果再启动工程,则控制台中打印的 Bean 名称中不会再包含helloController,而且我们再访问 /hello 请求时也会提示404错误,这就说明组件扫描的默认规则的确是以 Spring Boot 主启动类为基准。


如果需要修改组件扫描的路径,我们可以在 @SpringBootApplication 注解中修改 scanBasePackages 或 scanBasePackageClasses 属性,这两个属性分别对应 @ComponentScan 注解的 basePackages 和 basePackageClasses 属性,比如我们将工程的组件扫描根包改为com.yangjunbo.springboot,此时再重启工程,控制台中就会再次打印 helloController,访问 /hello 请求时也可以正常得到响应。

