读书笔记《第一部分 使用 Spring Boot 开始微服务开发》第2章Spring Boot简介
PART I
Getting Started with Microservice Development Using Spring Boot
在这一部分中,您将学习如何使用 Spring Boot 的一些最重要的特性来开发微服务。
本部分包括以下章节:
- 第 1 章,微服务简介
- 第二章,Spring Boot简介
- 第 3 章,创建一组协作微服务
- 第 4 章,使用 Docker 部署我们的微服务
- 第 5 章,使用 OpenAPI 添加 API 描述
- 第 6 章,添加持久性
- 第 7 章,开发响应式微服务
Introduction to Spring Boot
在本章中,我们将介绍如何使用 Spring Boot 构建一组协作的微服务,重点介绍如何开发能够交付业务价值的功能。我们在前一章中指出的微服务挑战将仅在一定程度上加以考虑,但将在后面的章节中全面讨论。
我们将开发包含基于普通 Spring Beans 的业务逻辑的微服务,并使用 Spring WebFlux 公开 REST API。 API 将根据 OpenAPI 规范使用 springdoc-openapi 进行记录。为了使微服务处理的数据持久化,我们将使用 Spring Data 将数据存储在 SQL 和 NoSQL 数据库中。
自 2018 年 3 月发布 Spring Boot v2.0 以来,开发响应式微服务变得更加容易,包括非阻塞同步 REST API。为了开发基于消息的异步服务,我们将使用 Spring Cloud Stream。请参阅第 1 章,微服务简介,反应式微服务部分,了解更多信息。
最后,我们将使用 Docker 将我们的微服务作为容器运行。这将允许我们使用单个命令启动和停止我们的微服务环境,包括数据库服务器和消息代理。
那是很多技术和框架,所以让我们简要介绍一下它们,看看它们是关于什么的!
在本章中,我们将介绍以下开源项目:
- Spring Boot
- Spring WebFlux
- springdoc-openapi
- Spring Data
- Spring Cloud Stream
- Docker
有关每个产品的更多详细信息将在接下来的章节中提供。
技术要求
本章不包含任何可以下载的源代码,也不需要安装任何工具。
Spring Boot
Spring Boot,以及 Spring Boot 所基于的 Spring Framework,是一个很好的 Java 微服务开发框架。
早在 2004 年 Spring 框架在 v1.0 中发布时,其主要目标之一就是解决过于复杂的J2EE标准(Java 2 Platforms, Enterprise Edition 的缩写)及其臭名昭著的重量级部署描述符。 Spring 框架基于依赖注入的概念提供了一个更轻量级的开发模型。与 J2EE 中的部署描述符相比,Spring 框架还使用了更轻量级的 XML 配置文件。
更糟糕的是 J2EE 标准,重量级部署描述符实际上有两种类型:
2006年,J2EE 更名为Java EE,Java平台企业版强>。 2017 年,Oracle 向 Eclipse 基金会提交了 Java EE。 2018 年 2 月,Java EE 更名为 Jakarta EE。
多年来,虽然 Spring Framework 越来越受欢迎,但 Spring Framework 中的功能也显着增长。慢慢地,使用不再那么轻量级的 XML 配置文件设置 Spring 应用程序的负担变成了一个问题。
2014年,Spring Boot v1.0发布,解决了这些问题!
约定优于配置和胖 JAR 文件
Spring Boot 通过对如何设置从Spring Framework 和第三方产品,例如用于记录或连接到数据库的库。 Spring Boot 通过默认应用许多约定来做到这一点,从而最大限度地减少配置需求。每当需要时,可以通过逐个编写一些配置来覆盖每个约定。这种设计模式被称为 convention over configuration,并且最大限度地减少了对初始配置的需求。
在我看来,配置在需要时最好使用 Java 和注释编写。旧的基于 XML 的配置文件仍然可以使用,尽管它们比引入 Spring Boot 之前要小得多。
除了使用约定优于配置之外,Spring Boot 还支持基于独立 JAR 文件的运行时模型,也称为 fat JAR 文件。在 Spring Boot 之前,运行 Spring 应用程序的最常见方法是将其作为 WAR 文件部署在 Java EE Web 服务器上,例如 Apache Tomcat。 Spring Boot 仍然支持 WAR 文件部署。
一个胖 JAR 文件不仅包含应用程序本身的类和资源文件,还包含应用程序所依赖的所有 JAR 文件。这意味着胖 JAR 文件是运行应用程序所需的唯一 JAR 文件;也就是说,我们只需要将一个 JAR 文件传输到我们想要运行应用程序的环境,而不是传输应用程序的 JAR 文件以及应用程序所依赖的所有 JAR 文件。
启动胖 JAR 不需要单独安装 Java EE Web 服务器,例如 Apache Tomcat。相反,它可以通过 java -jar app.jar
等简单命令启动,使其成为在 Docker 容器中运行的完美选择!例如,如果 Spring Boot 应用程序使用 HTTP 公开 REST API,它还将包含一个嵌入式 Web 服务器。
设置 Spring Boot 应用程序的代码示例
我们将在这里只看一些小的代码片段来指出主要功能。对于一个完整的示例,您必须等到下一章!
神奇的@SpringBootApplication 注解
基于约定的自动配置机制可以通过注解应用类来启动,即包含静态main
方法,带有 @SpringBootApplication
注释。以下代码显示了这一点:
此注释将提供以下功能:
- 它启用组件扫描,即在应用程序类及其所有子包的包中查找Spring组件和配置类。
- 应用程序类本身成为一个配置类。
- 它启用了自动配置,Spring Boot 在其可以自动配置的类路径中查找 JAR 文件。例如,如果类路径中有 Tomcat,Spring Boot 会自动将 Tomcat 配置为嵌入式 Web 服务器。
组件扫描
让我们假设我们在应用程序类的包(或其子包之一)中有以下 Spring 组件:
应用程序中的另一个组件可以自动注入该组件,也称为 auto-wiring,使用 @Autowired
注释:
我更喜欢使用构造函数注入(通过字段和 setter 注入)来保持组件中的状态不可变。如果您希望能够在多线程运行时环境中运行组件,则不可变状态很重要。
如果我们想要使用在应用程序包之外的包中声明的组件,例如多个Spring Boot应用程序共享的实用程序组件,我们可以补充@ComponentScan
注释的应用程序类中的 "Code-In-Text--PACKT-">@SpringBootApplication 注释:
我们现在可以从应用程序代码中的 se.magnus.util
包中自动连接组件,例如,名为 MyUtility
,如下:
此实用程序组件可以自动连接到应用程序组件中,如下所示:
基于 Java 的配置
如果我们想覆盖 Spring Boot 的默认配置或者我们想添加自己的配置,我们可以简单地用 @Configuration
注释一个类将被 我们之前描述的组件扫描机制拾取。
例如,如果我们想在处理 HTTP 请求(由 Spring WebFlux 处理,下一节介绍)中设置一个过滤器,在处理的开始和结束时写入一条日志消息,我们可以配置一个日志过滤器,如下:
我们也可以将配置直接放在应用程序类中,因为 @SpringBootApplication
注释意味着 @Configuration
注释。
现在我们已经了解了 Spring Boot,让我们来谈谈 Spring WebFlux。
Spring WebFlux
Spring Boot 2.0 基于 Spring Framework 5.0,它内置了对开发 响应式应用程序的支持。 Spring 框架使用 Project Reactor 作为其响应式支持的 基础实现,并且还附带了一个新的 Web 框架 Spring WebFlux,支持响应式,即非阻塞,HTTP客户端和服务的开发。
- 基于注解的命令式风格,类似于现有的 Web 框架 Spring Web MVC,但支持响应式服务
- 基于路由器和处理程序的面向函数的新模型
在本书中,我们将使用基于注解的命令式风格来演示将 REST 服务从 Spring Web MVC 迁移到 Spring WebFlux 是多么容易,然后开始重构服务以使它们成为完全反应式。
Spring WebFlux 还提供了一个完全响应式 HTTP 客户端,WebClient
,作为对现有 RestTemplate
客户端。
Spring WebFlux 支持在基于 Servlet 规范 v3.1 或更高版本的 servlet 容器上运行,例如 Apache Tomcat,但也支持响应式非基于 Servlet 的 嵌入式 Web 服务器,例如Netty(https://netty.io/)。
Servlet 规范是 Java EE 平台中的规范,它标准化了如何开发使用 Web 协议(如 HTTP)进行通信的 Java 应用程序。
设置 REST 服务的代码示例
在我们可以基于 Spring WebFlux 创建 REST 服务之前,我们需要将 Spring WebFlux(以及 Spring WebFlux 所需的依赖项)添加到类路径中,以便检测和配置 Spring Boot在启动期间。 Spring Boot 提供了大量方便的starter dependencies,它们引入了特定的特性,以及每个特性通常需要的依赖关系。所以,让我们使用 Spring WebFlux 的 starter 依赖,然后看看一个简单的 REST 服务是什么样子的!
启动器依赖项
在本书中,我们将使用 Gradle 作为我们的构建工具,因此 Spring WebFlux 启动器依赖 将被添加到 build.gradle
文件。它看起来像这样:
您可能想知道为什么我们不指定版本号。当我们查看第 3 章中的一个完整示例时,我们将讨论这一点,创建一组协作微服务!
当微服务启动时,Spring Boot 会在 classpath 上检测 Spring WebFlux 并对其进行配置,以及启动嵌入式 Web 服务器等其他事情。 Spring WebFlux 默认使用 Netty,我们可以从日志输出中看到:
如果我们想从 Netty 切换到 Tomcat 作为我们的嵌入式 Web 服务器,我们可以通过从启动器依赖项中排除 Netty 并为 Tomcat 添加启动器依赖项来覆盖默认配置:
重启微服务后,我们可以看到 Spring Boot 选择了 Tomcat:
属性文件
正如您在前面的示例中所见,Web 服务器是使用端口 8080
启动的.如果要更改端口,可以使用属性文件覆盖默认值。 Spring Boot 应用程序属性文件可以是 .properties
文件或 YAML
文件。默认情况下,它们被命名为 application.properties
和 application.yml< /code>,分别。
在本书中,我们将使用 YAML 文件,以便将嵌入式 Web 服务器使用的 HTTP 端口更改为,例如,7001
。通过这样做,我们可以避免与在同一服务器上运行的其他 微服务发生端口冲突。为此,我们可以在 application.yml
文件中添加以下行:
当我们在第 4 章中开始将微服务开发为容器时,使用 Docker 部署我们的微服务,端口冲突将不再是问题。每个容器都有自己的主机名和端口范围,因此所有微服务都可以使用,例如,端口 8080
而不会相互冲突。
示例 RestController
现在,有了 Spring WebFlux 和我们选择的嵌入式 Web 服务器,我们可以像使用 Spring MVC 时一样编写 REST 服务 ,即作为 < code class="Code-In-Text--PACKT-">RestController:
listResources()
方法上的 @GetMapping
注释将 Java 方法映射到 主机上的 HTTP
网址。 GET
API: 8080/myResourceList<Resource>
类型的返回值会被转换成JSON。
现在我们已经讨论了 Spring WebFlux,让我们看看如何记录我们使用 Spring WebFlux 开发的 API。
springdoc-openapi
开发 API(例如 RESTful 服务)的一个非常重要的方面是如何记录它们以使其易于使用。 SmartBear Software 的 Swagger 规范是记录 RESTful 服务的最广泛使用的方法之一。许多领先的 API 网关都原生支持使用 Swagger 规范公开 RESTful 服务的文档。
2015年,SmartBear 软件在OpenAPI Initiative<下向Linux基金会捐赠了Swagger规范 /strong> 并创建了 OpenAPI 规范。 SmartBear Software 提供的工具仍然使用 Swagger 这个名称。
springdoc-openapi 是一个开源项目,独立于 Spring Framework,可以在运行时创建 基于 OpenAPI 的 API 文档.它通过检查应用程序来做到这一点,例如,检查基于 WebFlux 和 Swagger 的注释。
我们将在接下来的章节中查看完整的源代码示例,但现在,示例 API 文档的以下精简屏幕截图(删除的部分标有“...”)可以做到:

图 2.1:使用 Swagger UI 可视化的示例 API 文档
注意大的 Execute 按钮,它可以用来实际试用 API,而不仅仅是阅读它的文档!
springdoc-openapi 帮助 我们记录微服务公开的 API。现在,让我们继续讨论 Spring Data。
弹簧数据
Spring Data 自带了一个通用的编程模型,用于在各种类型的数据库引擎中持久化数据,从传统的关系数据库(SQL 数据库)到各种类型的 NoSQL 数据库引擎,例如文档数据库(例如 MongoDB)、键值数据库(例如 Redis)和图数据库(例如 Neo4J)。
Spring Data 项目分为几个子项目,在本书中,我们将使用 Spring Data 子项目,用于映射到 MySQL 数据库的 MongoDB 和 JPA。
JPA 代表 Java Persistence API,是关于如何处理 关系数据。请转到 https://jcp.org/ aboutJava/communityprocess/mrel/jsr338/index.html 用于 最新规范,在撰写本文时是 JPA 2.2。
Spring Data中编程模型的两个核心概念是entities和repositories。实体和 存储库概括了如何从各种类型的数据库存储和访问数据。它们提供了一个通用的抽象,但仍然支持向实体和存储库添加特定于数据库的行为。在我们继续阅读本章时,将简要解释这两个核心概念以及一些说明性代码示例。请记住,更多细节将在接下来的章节中提供!
尽管 Spring Data 为不同类型的数据库提供了通用的编程模型,但这并不意味着您将能够编写可移植的源代码。例如,如果不对源代码进行一些更改,通常不可能将数据库技术从 SQL 数据库切换到 NoSQL 数据库!
实体
实体描述将由 Spring Data 存储的数据。通常,实体类是用通用的 Spring Data 注释和特定于每种数据库技术的注释进行注释的 。
例如, 将存储在关系数据库中的实体可以使用 JPA 注释进行注释,如下所示:
如果要将实体存储在 MongoDB 数据库中,则 Spring Data MongoDB 子项目中的注释可以与通用 Spring Data 注释一起使用。例如,考虑以下代码:
@Id
和 @Version
注释 是通用注释,而 @Document
注释 特定于 Spring Data MongoDB 子项目。
这可以通过研究导入语句来揭示;包含 mongodb
的导入语句来自 Spring Data MongoDB 子项目。
存储库
存储库用于存储和访问来自不同类型数据库的数据。在其最基本的 形式中,可以将存储库声明为 Java 接口,并且 Spring Data 将使用自以为是的约定即时生成其实现。这些 约定可以被其他配置覆盖和/或补充,如果需要,还可以使用一些 Java 代码。 Spring Data 还附带了一些基本的 Java 接口,例如,CrudRepository
,以使存储库的定义更加简单。基本接口 CrudRepository
为我们提供了创建、读取、更新和删除操作的标准方法。
要指定用于处理 JPA 实体 ReviewEntity
的存储库,我们只需要声明以下内容:
在此示例中,我们使用类 ReviewEntityPK
来描述复合主键。它看起来如下:
我们还添加了一个额外的方法,findByProductId
,它允许我们查找 Review
实体基于 productid
- 一个作为主键一部分的字段。该方法的命名遵循 Spring Data 定义的命名约定,该约定允许 Spring Data 动态生成此方法的实现。
如果我们希望 使用存储库,我们可以简单地注入它然后开始使用它,例如:
除了 CrudRepository
接口之外,Spring Data 还提供了一个响应式基础接口 ReactiveCrudRepository
,启用响应式存储库。该接口中的方法不返回对象或对象集合;相反,它们 返回 Mono 和 Flux 对象。我们将看到 Mono
和 Flux
对象在 第 7 章,开发反应式微服务,反应式流能够返回 0...1
或 0...m
实体,因为它们在流中可用。基于响应式的接口只能由支持响应式数据库驱动的 Spring Data 子项目使用;也就是说,它们基于非阻塞 I/O。 Spring Data MongoDB 子项目支持响应式存储库,而 Spring Data JPA 不支持。
如前所述,指定用于处理 MongoDB 实体的响应式存储库 RecommendationEntity
可能类似于以下内容:
Spring Data 部分到此结束。现在让我们看看我们如何使用 Spring Cloud Stream 开发基于消息的异步服务。
Spring Cloud Stream
这一部分我们不会关注 Spring Cloud;我们将在本书的 Part 2 中这样做,从 Chapter 8, Introduction to Spring云, 到 第 14 章,理解分布式跟踪。但是,我们将引入属于 Spring Cloud 的模块之一:Spring Cloud Stream。 Spring Cloud Stream 基于 publish and subscribe 集成模式提供消息传递的流式抽象。 Spring Cloud Stream 目前内置了对 Apache Kafka 和 RabbitMQ 的支持。存在许多提供与其他流行消息传递系统集成的单独项目。请参阅 https://github.com/spring-cloud?q=活页夹了解更多详情。
- Message:一种数据结构,用于描述发送到消息系统和从消息系统接收的数据。
- Publisher:将消息发送到消息系统,也称为Supplier .
- Subscriber:接收来自消息系统的消息,也称为Consumer .
- Destination:用于与消息系统通信。发布者使用输出目的地,订阅者使用输入目的地。目的地由特定的绑定器映射到底层消息系统中的队列和主题。
- Binder:binder 提供与特定消息传递系统的实际集成,类似于 JDBC 驱动程序对特定类型数据库所做的工作。
要使用的实际消息传递系统在运行时确定,具体取决于在 类路径中找到的内容。 Spring Cloud Stream 带有关于如何处理消息的自以为是的约定。这些约定可以通过指定消息传递特性的配置来覆盖,例如消费者组、分区、持久性、持久性和错误处理;例如,重试和死信队列处理。
发送和接收消息的代码示例
为了更好地了解所有这些如何组合在一起,让我们看一些源代码示例。
Spring Cloud Stream 带有两种编程模型:一种基于注释使用的旧的和现在已弃用的模型(例如,@EnableBinding
、@Output
和 @StreamListener
) 和一个基于新模型的关于写作功能。在本书中,我们将使用函数式实现。
要实现发布者,我们只需要将 java.util.function.Supplier
功能接口实现为 Spring Bean。例如,以下是发布者将消息作为字符串发布:
订阅者被实现为实现 java.util.function.Consumer
功能接口的 Spring Bean。例如,以下是一个将消息作为字符串消费的订阅者:
也可以定义一个处理消息的 Spring Bean,这意味着它既消费又发布消息。这可以通过实现 java.util.function.Function
功能接口来完成。例如,一个 Spring Bean 使用传入的消息并在经过一些处理后发布一条新消息(在此示例中,两条消息都是字符串):
为了让 Spring Cloud Stream 知道这些函数,我们需要使用 spring.cloud.function.definition
配置属性来声明它们。例如,对于前面定义的三个函数,如下所示:
最后,我们需要告诉 Spring Cloud Stream 每个函数使用哪个目的地。要连接 我们的三个函数,以便我们的处理器使用来自发布者的消息,而我们的订阅者使用来自处理器的消息,我们可以提供以下配置:
这将导致以下消息流:
供应商默认每秒由 Spring Cloud Stream 触发,因此如果我们启动一个包含前面描述的功能和配置的 Spring Boot 应用程序,我们可以预期如下输出:
在供应商应该由外部事件而不是使用计时器触发的情况下,可以使用 StreamBridge
辅助类。例如,如果一个消息应该在一个 REST API 时发布到处理器,sampleCreateAPI
, 被调用,代码可能如下所示:
现在我们了解了各种 Spring API,让我们在下一节中了解一下 Docker 和容器。
Docker
我假设 Docker 和容器的概念不需要深入介绍。 Docker 在 2013 年将容器的概念作为虚拟机的轻量级替代品非常流行。容器实际上是使用 Linux namespaces 到 提供不同容器之间的隔离,在它们使用全局系统资源(如用户、进程、文件系统和 网络。 Linux 控制组(也称为cgroups)用于限制允许容器消耗的 CPU 和内存量。
与使用管理程序在每个虚拟机中运行操作系统的完整副本的虚拟机相比,容器中的开销只是传统虚拟机中开销的一小部分。这会导致更快的启动时间并显着降低 CPU 和内存使用方面的开销。
但是,为容器提供的隔离并不像为虚拟机提供的隔离那样安全。随着 Windows Server 2016 的发布,微软支持在 Windows 服务器中使用 Docker。
在过去的几年里,一种轻量级的虚拟机形式已经发展起来。它混合了传统虚拟机和容器的优点,为虚拟机提供与容器类似的占用空间和启动时间,并提供与传统虚拟机相同级别的安全隔离。一些示例包括 Amazon Firecracker 和 Microsoft Windows Subsystem for Linux v2 (WSL2)。如需更多信息,请参阅https: //firecracker-microvm.github.io 和 https://docs.microsoft.com/en-us/windows/wsl/。
容器在开发和测试期间都非常有用。能够使用单个测试命令启动协作微服务和资源管理器(例如,数据库服务器、消息传递代理等)的完整系统环境简直令人惊叹。
例如,我们可以编写脚本来自动化我们的微服务环境的端到端测试。测试脚本可以启动微服务环境,使用 公开的 API 运行测试,并拆除微服务环境。这种类型的自动化测试脚本非常有用,既可以在将代码推送到源代码存储库之前在开发人员 PC 上本地运行,也可以作为交付管道中的一个步骤执行。每当开发人员将代码推送到源存储库时,构建服务器就可以在其持续集成和部署过程中运行这些类型的测试。
对于生产使用,我们需要一个容器编排器,例如 Kubernetes。我们将在本书的后面部分回到容器编排器和 Kubernetes。
对于我们将在本书中看到的大多数 微服务,运行如下所示的 Dockerfile微服务作为 Docker 容器:
如果我们想用一个命令启动和停止多个容器,Docker Compose 是完美的工具。 Docker Compose 使用 YAML 文件来描述要管理的容器。对于我们的微服务,它可能如下所示:
让我稍微解释一下前面的源代码:
build
指令用于指定每个微服务使用哪个 Dockerfile。 Docker Compose 将使用它来构建 Docker 映像,然后基于该 Docker 映像启动 Docker 容器。- 复合服务的
ports
指令用于暴露端口8080< /code> 在 Docker 运行的服务器上。在开发人员的机器上,这意味着只需使用
localhost:8080
即可访问复合服务的端口!
YAML 文件中的所有 容器都可以通过如下简单命令进行管理:
docker-compose up -d
:启动所有容器。-d
表示容器在后台运行,不会锁定执行命令的终端。docker-compose down
:停止并移除所有容器。docker-compose logs -f --tail=0
:打印所有容器的日志信息。-f
表示该命令不会完成,而是等待新的日志消息。--tail=0
表示我们不想看到任何以前的日志消息,只希望看到新的。
有关 Docker Compose 命令的完整列表,请参阅 https://docs.docker.com/compose/reference/。
这是对 Docker 的简要介绍。我们将从第 4 章,使用 Docker 部署我们的微服务开始详细介绍 Docker。
概括
在本章中,我们介绍了 Spring Boot 和可用于构建协作微服务的补充开源工具。
Spring Boot 用于简化基于 Spring 的生产就绪应用程序的开发,例如微服务。在如何从 Spring Framework 和第三方工具中设置核心模块方面,人们有强烈的意见。使用 Spring WebFlux,我们可以开发暴露反应式的微服务,即非阻塞的 REST 服务。要记录这些 REST 服务,我们可以使用 springdoc-openapi 为 API 创建基于 OpenAPI 的文档。如果我们需要持久化微服务使用的数据,我们可以使用 Spring Data,它为使用实体和存储库访问和操作持久化数据提供了一个优雅的抽象。 Spring Data 的编程模型类似,但不能在不同类型的数据库之间完全可移植,例如关系数据库、文档数据库、键值数据库和图形数据库。
如果我们更喜欢在我们的微服务之间异步发送消息,我们可以使用 Spring Cloud Stream,它提供了对消息传递的流抽象。 Spring Cloud Stream 为 Apache Kafka 和 RabbitMQ 提供了开箱即用的支持,但可以使用自定义绑定器进行扩展以支持其他消息传递代理。最后,Docker 使容器的概念成为易于使用的虚拟机的轻量级替代品。基于 Linux 命名空间和控制组,容器提供类似于传统虚拟机提供的隔离,但在 CPU 和内存使用方面的开销显着降低。
在下一章中,我们将迈出第一步,使用 Spring Boot 和 Spring WebFlux 创建具有简约功能的微服务。
问题
@SpringBootApplication
注解的目的是什么?- 用于开发 REST 服务的旧 Spring 组件、Spring Web MVC 和新的 Spring WebFlux 之间的主要区别是什么?
- springdoc-openapi 如何帮助开发人员记录 REST API?
- Spring Data 中存储库的功能是什么?最简单的存储库实现是什么?
- Spring Cloud Stream 中的 binder 的目的是什么?
- Docker Compose 的目的是什么?