vlambda博客
学习文章列表

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

Building Microservices Using Spring Boot 2

在本书的共同历程中,我们见证了 Java、Jakarta EE 和 MicroProfile.io 如何快速发展,以使那些使用 Java EE 构建软件架构的人能够构建云就绪应用程序。

我们的目的不是表明一个开发平台比另一个更好。我们的目的是根据与 Java EE 过时版本及其单体架构模型相关的反馈,尝试消除对 Jakarta EE 和 MicroProfile.io 的许多预先判断。

出于这个原因,在本附录中,我们将了解如何通过 Spring Boot 创建与前几章中开发的相同的微服务。

您可以在 GitHub 存储库中找到本章的代码,位于 https://github.com/PacktPublishing/Hands-On-Cloud-Native-Microservices-with-Jakarta-EE/tree/master/Chapter11

Spring Boot

Spring Boot 是一个很棒的框架,可以帮助开发人员轻松构建和运行微服务和云原生应用程序。

从历史上看,它代表了 Java EE 的第一个替代方案,而且在我看来,它通常以生产就绪的方式实现新的架构设计模式

多年来,它已经发展到克服项目的开源社区提出的主要关键问题,如下所示:

  • There are too many XML configuration files needed to implement it
  • It is a difficult way to manage the interdependencies between Spring modules

正如在 Thorntail 中描述的那样,可以使用以下方法执行 Spring Boot:

  • Using an executable JAR file, via the $ java -jar command, with the following embedded servlet containers:
    • Tomcat 8.5
    • Jetty 9.4
    • Undertow 1.4
  • Via traditional WAR deployments into any application servers or servlet containers that implement the Servlet 3.1+ specifications

最新版本(在撰写本文时为 v2.0.5)提供使用 Maven 或 Gradle 的构建支持;它需要 Java 8 或更高版本才能运行,并且它也是基于代表主核心的 Spring 5.0.9 版本。

Maven settings

Apache Maven 可能是最常见的构建管理系统:我们可以将其视为 Java 应用程序事实上的构建和打包操作。在本节中,我们将使用它来构建和打包我们的应用程序。

Spring Boot 与 Apache Maven 3.2 或更高版本兼容,并且为了轻松管理所有 Spring Boot 依赖项(特别是正确的版本),您可以将 Maven POM 文件设置为从 spring-boot 继承-starter-parent 项目。

以下是您可以在 Spring Boot 应用程序中使用的 pom.xml 文件的示例:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

     <!-- Inherit defaults from Spring Boot -->
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
         <version>2.0.5.RELEASE</version>
     </parent>

     <!-- Add typical dependencies for a web application -->
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
     </dependencies>

     <!-- Package as an executable jar -->
     <build>
         <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

像往常一样,您将能够使用以下命令构建和打包您的应用程序:

$ mvn clean package

Gradle settings

Gradle 是一个开源构建自动化工具,它使用用 Groovy 或 Kotlin DSL 编写的脚本。它受到主要 IDE 的支持,您可以使用命令行界面或通过持续集成服务器运行它。

安装后,您可以通过从项目的根目录启动以下命令来创建一个新项目(或自动将现有的 Maven 项目转换为 Gradle 项目):

$ gradle init

为了在 Gradle 项目中使用 Spring Boot,您可以创建一个 Gradle 文件,如下所示:

plugins {
     id 'org.springframework.boot' version '2.0.5.RELEASE'
     id 'java'
}

jar {
     baseName = 'myproject'
     version = '0.0.1-SNAPSHOT'
}

repositories {
     jcenter()
}

dependencies {
     implementation 'org.springframework.boot:spring-boot-dependencies:2.0.5.RELEASE'
     implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

在这个 XML 文件中,我添加了 Spring Boot BOM 文件作为第一个依赖项,并指定了 Spring Boot 版本,以便使用所有 Spring Boot 模块的正确版本。

要构建可执行 JAR 文件,可以执行以下命令:

$ ./gradlew mySpringBootJar

之后,您可以通过执行以下命令来运行它:

$ java -jar build/libs/gradle-my-spring-boot-project.jar

或者您可以执行以下 Gradle 命令:

$ ./gradlew bootRun

Upgrading from an earlier version of Spring Boot

Spring Boot 实现了一项功能,使开发人员能够分析应用程序的环境并在应用程序启动时打印结果。它还可以使用 properties migrator starter 自动迁移应用程序属性。

要激活环境,您应该将以下 Maven 依赖项添加到您的项目中:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-properties-migrator</artifactId>
     <scope>runtime</scope>
</dependency>

完成分析和迁移后,请确保从项目的依赖项中删除此模块。

Building Spring Boot microservices

在本节中,我们将实现与 第 4 章< /a>,使用 Thorntail 构建微服务。我们将分析应用程序的服务器端层的细节,(因为客户端层保持不变,使用 Angular 6 实现)。

结果将是一个处理足球运动员域的简单足球运动员微服务;它将公开 CRUD API,并将使用 PostgreSQL 数据库存储和检索信息。

您可以完成第4章中构建的整个应用程序, 使用 Thorntail 构建微服务, 使用我们将在本章中实现的方法。

为了构建我们的应用程序,我们将使用以下工具,并且对于每个工具,我们将指定安装它所需的信息:

Project details

在本节中,我们将为我们的微服务构建源代码。为此,除了前面描述的先决条件外,您还需要在系统上安装 PostgreSQL。正如我之前提到的,我们将使用 Docker 来安装和处理 PostgreSQL。我使用 macOS High Sierra 作为我的工作环境。如果您使用 Thorntail 实现了相同的项目(如 第 4 章中所述, 使用 Thorntail 构建微服务,请随意跳过此部分;否则,请按照说明如何在 Docker 容器中安装和运行 PostgreSQL 的说明进行操作。

Database installation and configuration

在您的机器上安装 Docker 后,就该运行容器化版本的 PostgreSQL。为此,请打开一个新的终端 window 并启动以下命令:

$ docker run --name postgres_springboot -e POSTGRES_PASSWORD=postgresPwd -e POSTGRES_DB=football_players_registry -d -p 5532:5432 postgres

此命令触发从 Docker 的公共注册表中提取标记为最新的 PostgreSQL 版本,下载运行容器所需的所有层,如以下代码片段所示:

Unable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
683abbb4ea60: Pull complete
c5856e38168a: Pull complete
c3e6f1ceebb0: Pull complete
3303bcd00128: Pull complete
ea95ff44bf6e: Pull complete
ea3f31f1e620: Pull complete
234873881fb2: Pull complete
f020aa822d21: Pull complete
27bad92d09a5: Pull complete
6849f0681f5a: Pull complete
a112faac8662: Pull complete
bc92d0ab9365: Pull complete
9e87959714b8: Pull complete
ac7c29b2bea7: Pull complete
Digest: sha256:d99f15cb8d0f47f0a66274afe30102b5bb7a95464d1e25acb66ccf7bd7bd8479
Status: Downloaded newer image for postgres:latest
83812c6e76656f6abab5bf1f00f07dca7105d5227df3b3b66382659fa55b5077

之后,PostgreSQL 镜像将作为容器启动。要验证这一点,您可以启动 $ docker ps -a 命令,为您提供已创建容器及其相关状态的列表:

CONTAINER ID IMAGE COMMAND CREATED
073daeefc52 postgres "docker-entrypoint.s..." Less than a second ago
STATUS PORTS NAMES
Up 4 seconds 0.0.0.0:5532->5432/tcp postgres_springboot

我不得不将命令结果分成两行,以使其可读。

您还可以检查容器日志,以检索有关 PostgreSQL 状态的信息。启动以下命令:

$ docker logs -f 1073daeefc52

1073daeefc52 是容器 ID。您应该看到以下信息:

PostgreSQL init process complete; ready for start up.

2018-07-13 22:53:36.465 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
 2018-07-13 22:53:36.466 UTC [1] LOG: listening on IPv6 address "::", port 5432
 2018-07-13 22:53:36.469 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"

现在是时候连接到容器来管理它了。启动以下命令:

$ docker exec -it 1073daeefc52 bash

1073daeefc52 是容器 ID。现在使用以下命令登录 PostgreSQL:

$ psql -U postgres

现在您将能够与数据库服务器进行交互,如下所示:

psql (10.4 (Debian 10.4-2.pgdg90+1))
Type "help" for help.
postgres=#

您应该能够看到我们在创建容器时创建的 football_players_registry 数据库。运行 \l 命令验证数据库列表:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

好的;是时候创建一个简单的表格来存放足球运动员的数据了。使用以下命令连接到 football_players 数据库:

$ \connect football_players_registry

使用以下命令创建表:

CREATE TABLE FOOTBALL_PLAYER(
ID SERIAL PRIMARY KEY NOT NULL,
NAME VARCHAR(50) NOT NULL,
SURNAME VARCHAR(50) NOT NULL,
AGE INT NOT NULL,
TEAM VARCHAR(50) NOT NULL,

POSITION VARCHAR(50) NOT NULL,
PRICE NUMERIC
);

使用以下命令检查表的结构:

$ \d+ football_player

您应该看到以下结果:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

Creating the source code

我们已经安装并配置了创建微服务以管理玩家注册表所需的一切。现在是时候编写公开我们的微服务 API 所需的代码了。

我们将使用 Spring Initializr 项目生成器实用程序 (https://start.spring.io/) 以便得到一个项目框架来工作。如前所述,我们的微服务必须显示允许我们执行 CRUD 操作的 API。

为了实现我们的微服务,我们将使用以下组件:

  • Web: This contains all the modules needed for full web development with a Tomcat servlet container and Spring MVC.
  • Actuator: This provides production-ready features, to help you monitor and manage your application.
  • DevTools: These are Spring Boot development tools.
  • JPA: This is the Java Persistence API, including spring-data-jpa, spring-orm, and Hibernate.
  • PostgreSQL: This is the PostgreSQL JDBC driver.

我们将使用 com.packtpub.springboot 作为项目的 Maven Group 名称,并将 football-player-microservice 作为工件。在项目表单生成器中设置这些值,如以下屏幕截图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

单击 Generate Project 以创建和下载带有项目骨架的 ZIP 文件。

将文件解压缩到您选择的目录,然后使用您喜欢的 IDE(Eclipse、NetBeans、IntelliJ 等)打开 Maven 项目。

该项目的核心元素是 Maven pom.xml 文件,其中包含实现我们的微服务所需的所有依赖项。

项目使用spring-boot-starter-parent来正确管理依赖管理,如下:

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.0.5.RELEASE</version>
     <relativePath/> <!-- lookup parent from repository -->
</parent>

我们还将项目名称的值从 football-player-microservice 更改为 Spring Boot Football player microservice

<modelVersion>4.0.0</modelVersion>
<groupId>com.packtpub.springboot</groupId>
<artifactId>football-player-microservice</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Spring Boot Football player microservice</name>
<description>Demo project for Spring Boot</description>

在启动我们的第一个构建之前,我们需要设置数据库设置,以禁用 Spring Boot 实例化内存数据库的默认行为。

因此,让我们进入 src/main/resources 目录并在 application.properties 文件中设置此值,如下所示:

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)

spring.datasource.driver-class-name=org.postgresql.Driver

spring.datasource.url=jdbc:postgresql://localhost:5532/football_players_registry

spring.datasource.username= postgres

spring.datasource.password=postgresPwd

# This property always initialize the database using sql scripts set under resources directory

spring.datasource.initialization-mode=always

# The SQL dialect makes Hibernate generate better SQL for the chosen database

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

spring.jpa.hibernate.ddl-auto=none

现在是时候启动第一个构建了,使用以下命令:

$ mvn clean package

这样,我们将下载所有依赖项并创建名为 football-player-microservice-0.0.1-SNAPSHOT.jarArtifact, >。

要检查项目是否可以使用,我们可以使用以下命令运行它:

$ java -jar target/football-player-microservice-0.0.1-SNAPSHOT.jar

我们可以看到 Spring Boot 正在运行,并且应用程序已部署:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

我们还没有实现 RESTful API;因此,我们将使用执行器健康检查来验证应用程序是否已正确启动。

调用http://localhost:8080/actuator/health,查看结果为STATUS: "UP",如下图所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

现在让我们使用 Ctrl + C 命令停止 Spring Boot 创建的 Tomcat,并开始更新我们的项目。

Entity class – JPA

我们需要一个域模型对象来映射插入到我们数据库中的记录。为此,我们将使用 JPA 规范;因此,我们将为此创建一个实体类。

让我们创建一个新的 Java 包来存储域模型对象,并将其命名为 model。完全限定的包名称将是 com.packtpub.springboot.footballplayermicroservice.model

接下来,我们将构建域类,命名为 FootballPlayer

...

import java.io.Serializable;
import java.math.BigInteger;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;

/**
* Domain model class that maps the data stored into football_player table
* inside database.
*
* @author Mauro Vocale
* @version 1.0.0 29/09/2018
*/
@Entity
@Table(name = "football_player")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "FootballPlayer.findAll", query
= "SELECT f FROM FootballPlayer f")
})
public class FootballPlayer implements Serializable {

    private static final long serialVersionUID = -92346781936044228L;

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     @Basic(optional = false)
     @Column(name = "id")
     private Integer id;

     @Basic(optional = false)
     @NotNull
     @Size(min = 1, max = 50)
     @Column(name = "name")
     private String name;

     @Basic(optional = false)
     @NotNull
     @Size(min = 1, max = 50)
     @Column(name = "surname")
     private String surname;

     @Basic(optional = false)
     @NotNull
     @Column(name = "age")
     private int age;

     @Basic(optional = false)
     @NotNull
     @Size(min = 1, max = 50)
     @Column(name = "team")
     private String team;

     @Basic(optional = false)
     @NotNull
     @Size(min = 1, max = 50)
     @Column(name = "position")
     private String position;

     @Column(name = "price")
     private BigInteger price;

     public FootballPlayer() {
     }

     public FootballPlayer(String name, String surname, int age,
         String team, String position, BigInteger price) {
         this.name = name;
         this.surname = surname;
         this.age = age;
         this.team = team;
         this.position = position;
         this.price = price;
     }

...

如您所见,此代码与 Chapter 4, 中创建的代码相同使用 Thorntail 构建微服务。这意味着您不仅可以在 Java EE/Jakarta EE 项目中使用 JPA 规范,还可以在 Spring 框架中使用 JPA 规范。规格始终是可移植的。

Repository – JPA

Spring Data JPA 有一个引人注目的特性:能够在运行时从存储库接口自动创建存储库实现。

如果您创建一个扩展 org.springframework.data.repository.CrudRepository 接口的自定义存储库,您可以继承几个方法来处理您的实体持久性,包括保存、删除和查找实体的方法.

所以,让我们开始创建我们的自定义存储库实现。

com.packtpub.springboot.footballplayermicroservice.repository 包下构建一个新的存储库接口,名为 FootballPlayerRepository,并记住扩展 org.springframework.data。 repository.CrudRepository 接口:

import org.springframework.data.repository.CrudRepository;

/**
* FootballPlayerRepository extends the CrudRepository interface.

* The type of entity and ID that it works with, FootballPlayer and Integer, are

* specified in the generic parameters on CrudRepository.
* By extending CrudRepository, FootballPlayerRepository inherits several

* methods for working with FootballPlayer persistence, including methods for

* saving, deleting, and finding FootballPlayer entities.
*
* @author Mauro Vocale
* @version 30/09/2018
*/
public interface FootballPlayerRepository extends CrudRepository<FootballPlayer, Integer> {

}

要完成管理数据库访问操作,我们需要创建构建数据库模式和填充表所需的文件。我们将使用 Spring Boot 数据库初始化命名约定来完成它;因此,我们将在 src/main/resources 目录下创建以下文件:

  • schema.sql: The file with the table creation SQL commands
  • data.sql: The file with the SQL insert data commands

在这种情况下,我们不会像配置 Java EE/Jakarta EE 项目那样配置 persistence.xml 文件,因为所有必需的信息都在 application.properties *.sql 文件。

现在再次运行应用程序,使用以下命令:

$ mvn spring-boot:run

您会注意到 SQL 创建模式和加载数据脚本是由 Spring Boot 运行的嵌入式 Tomcat 执行的,如以下代码片段所示:

2018-09-30 00:20:46.424 INFO 7148 --- [ost-startStop-1] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from URL [file:/Users/mvocale/Progetti/Hands_on_Cloud_Native_Microservices_with_Java_EE/appendix-A/football-player-microservice/target/classes/schema.sql]
2018-09-30 00:20:46.433 INFO 7148 --- [ost-startStop-1] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from URL [file:/Users/mvocale/Progetti/Hands_on_Cloud_Native_Microservices_with_Java_EE/appendix-A/football-player-microservice/target/classes/schema.sql] in 9 ms.
2018-09-30 00:20:46.437 INFO 7148 --- [ost-startStop-1] o.s.jdbc.datasource.init.ScriptUtils : Executing SQL script from URL [file:/Users/mvocale/Progetti/Hands_on_Cloud_Native_Microservices_with_Java_EE/appendix-A/football-player-microservice/target/classes/data.sql]
2018-09-30 00:20:46.467 INFO 7148 --- [ost-startStop-1] o.s.jdbc.datasource.init.ScriptUtils : Executed SQL script from URL [file:/Users/mvocale/Progetti/Hands_on_Cloud_Native_Microservices_with_Java_EE/appendix-A/football-player-microservice/target/classes/data.sql] in 30 ms.

您还可以使用简单的查询来验证数据库状态,例如 SELECT * FROM football_player,它必须返回所有预加载 data.sql 文件的值。

The RESTful web service

Spring 框架提供了一个直观且易于使用的模型来公开您的数据,使用 RESTful Web 服务,这是微服务之间 API 通信的事实标准。

我们将在本节中使用的版本 2.0.5 自动将 CRUD 操作公开为 RESTful API。

正如我们在 Repository – JPA 部分中所描述的,我们的 FootballPlayerRepository 接口扩展了 org.springframework.data.repository.CrudRepository,它从该接口继承以下方法:

...

public <S extends T> S save(S s);

public <S extends T> Iterable<S> saveAll(Iterable<S> itrbl);

public Optional<T> findById(ID id);

public boolean existsById(ID id);

public Iterable<T> findAll();

public Iterable<T> findAllById(Iterable<ID> itrbl);

public long count();

public void deleteById(ID id);

public void delete(T t);

public void deleteAll(Iterable<? extends T> itrbl);

public void deleteAll();

使用 Spring Data Rest 模块,正如我之前提到的,您可以自动公开这些方法。为此,您应该将 spring-boot-starter-data-rest 依赖项插入到 Maven pom.xml 文件中,如下所示:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

然后编译并运行你的项目,如下:

$ mvn clean package && mvn spring-boot:run

您将能够获取应用程序中可用 API 的链接。如果您打开浏览器并调用 http://localhost:8080/,您将获得指向 http://localhost:8080/footballPlayers 的链接,如以下屏幕截图:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

通过调用 http://localhost:8080/footballPlayers,您将获得存储在应用程序中的足球运动员列表,如下所示:

{
   "_embedded" : {
     "footballPlayers" : [ {
     "name" : "Gianluigi",
     "surname" : "Buffon",
     "age" : 40,
     "team" : "Paris Saint Germain",
     "position" : "goalkeeper",
     "price" : 2,
     "_links" : {
     "self" : {
       "href" : "http://localhost:8080/footballPlayers/1"
     },
     "footballPlayer" : {
       "href" : "http://localhost:8080/footballPlayers/1"
     }
   }
},

{
   "name" : "Manuel",
   "surname" : "Neuer",
   "age" : 32,
   "team" : "Bayern Munchen",
   "position" : "goalkeeper",
   "price" : 35,
   "_links" : {
     "self" : {
       "href" : "http://localhost:8080/footballPlayers/2"
       },
     "footballPlayer" : {
       "href" : "http://localhost:8080/footballPlayers/2"
     }
   }
},
...
}

否则,在本节中,我们将定义旧式方法:我将创建一个服务和控制器类,以便调用 JPA 层(存储库)并公开 API。由于 SpringFox 框架中存在错误,无法使用 Swagger 来记录 API, 是良好微服务的关键特性之一。因此,我将遵循前面描述的方案,同时等待错误得到解决。

首先,我将创建一个服务类,用于解耦 REST API 控制器和数据访问层之间的业务逻辑。我只会实现基本的 CRUD 方法,而不是 CRUD 存储库接口提供的所有集合。

在我们的例子中,它的实现非常简单,而且看起来只是一个传递;在实际生产用例中,您可以使用业务逻辑操作,以坚持职责分离模式:

@Service
public class FootballPlayerService {

    @Autowired
    private FootballPlayerRepository repository;

    public Iterable<FootballPlayer> findAll() {
        return repository.findAll();
    }

    public FootballPlayer save(FootballPlayer entity) {
        return repository.save(entity);
    }

    public void deleteById(Integer id) {
        repository.deleteById(id);
    }

    public Optional<FootballPlayer> findById(Integer id) {

 return repository.findById(id);
    }

}

现在我们将使用 RESTful Web 服务公开我们的服务方法,如下所示:

@RestController
@RequestMapping("/footballplayer")
public class FootballPlayerRESTController {

    @Autowired
    private FootballPlayerService service;

    @RequestMapping(method = RequestMethod.GET, produces = "application/json")
    public Iterable<FootballPlayer> findAll() {
        return service.findAll();
    }

    @RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json")
    public FootballPlayer save(@RequestBody FootballPlayer entity) {
        return service.save(entity);
    }

    @RequestMapping(value = "/update/{id}", method = RequestMethod.PUT, produces = "application/json")
    public FootballPlayer edit(@PathVariable Integer id, @RequestBody FootballPlayer entity) {
        return service.save(entity);
    }

    @RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE, produces = "application/json")
    public void delete(@PathVariable Integer id) {
        service.deleteById(id);
    }

    @RequestMapping(value = "/show/{id}", method = RequestMethod.GET, produces = "application/json")
    public Optional<FootballPlayer> findById(@PathVariable Integer id) {
        return service.findById(id);
    }
}

如您所见,我们定义了以下内容:

  • The API paths
  • The API parameters
  • The producers and consumers payload types

现在我们可以调用检索足球运动员列表的 API,如下所示:

$ curl http://localhost:8080/footballplayer | json_pp

输出应该类似于以下内容(为方便起见,我们只显示了部分代码):

[
{
   "id":1,
   "name":"Gianluigi",
   "surname":"Buffon",
   "age":40,
   "team":"Paris Saint Germain",
   "position":"goalkeeper",
   "price":2
},
{
   "id":2,
   "name":"Manuel",
   "surname":"Neuer",
   "age":32,
   "team":"Bayern Munchen",
   "position":"goalkeeper",
   "price":35
},
{
   "id":3,
   "name":"Keylor",
   "surname":"Navas",
   "age":31,
   "team":"Real Madrid",
   "position":"goalkeeper",
   "price":18
},
...
]

最后,我们将创建 JUnit 测试,以确保我们的 API 正常工作。

让我们添加 Maven 依赖项,如下所示:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
</dependency>
<dependency>
     <groupId>com.jayway.jsonpath</groupId>
     <artifactId>json-path</artifactId>
     <scope>test</scope>
</dependency>

像往常一样,我没有指定版本,因为它是由 spring-boot-starter-parent BOM 自动处理的。

接下来,我将在由 Spring Initializr 实用程序创建的类 com.packtpub.springboot.footballplayermicroservice.FootballPlayerMicroserviceApplicationTests 中构建测试方法。

test 类如下所示:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = FootballPlayerMicroserviceApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class FootballPlayerMicroserviceApplicationTests {
    private final HttpHeaders headers = new HttpHeaders();

    private final TestRestTemplate restTemplate = new TestRestTemplate();

    @LocalServerPort
    private int port;

    @Test
    public void test_1_FindAll() throws IOException {
        System.out.println("findAll");
        HttpEntity<String> entity = new HttpEntity<>(null, headers);
        ResponseEntity<String> response =  
            restTemplate.exchange(createURLWithPort("/footballplayer"),                                                                                                                              
                HttpMethod.GET, entity, String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);

        JSONArray jsonArray = JsonPath.read(response.getBody(), "$.[*]");
        assertThat(23).isEqualTo(jsonArray.size());
    }

    @Test
    public void test_2_Create() {
        System.out.println("create");
        FootballPlayer player = new FootballPlayer("Mauro", "Vocale", 38, "Juventus", "central 
            midfielder", new BigInteger("100"));

        HttpEntity<FootballPlayer> entity = new HttpEntity<>(player, headers);
        ResponseEntity<String> response = restTemplate.exchange( 
            createURLWithPort("/footballplayer/save"),                                                                                         
                HttpMethod.POST, entity, String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isEqualTo(
"{\"id\":24,\"name\":\"Mauro\",\"surname\":\"Vocale\",\"age\":38,\"team\":\"Juventus\",\"position\":\"central midfielder\",\"price\":100}");
    }
    ....

让我们分析一下课程中最重要的部分。

我将使用 SpringRunner 类运行测试套件,正如在 @RunWith 注释中定义的那样。 SpringRunnerSpringJUnit4ClassRunner 类的别名,它具有创建 Spring 上下文和执行测试所需的实用程序。

我决定实现一个集成测试,所以我没有模拟任何东西;相反,我调用了真正的方法。

为此,我需要创建一个真正的 Spring 执行环境。使用 @SpringBootTest 注释,我将类设置为启动(在我们的例子中为 FootballPlayerMicroserviceApplication.class),以及执行嵌入式 servlet 容器的随机端口。

最后,我必须以明确定义的顺序执行测试,以避免与缺少测试记录相关的失败。为了做到这一点,我设置了 FixMethodOrder(MethodSorters.NAME_ASCENDING)。这样,Spring 根据方法的名称执行测试。这就是在方法名称中使用升序数字的原因。

Swagger documentation and OpenAPI

微服务架构将驱动企业架构生态构建海量的API,通过这些API可以暴露数据,实现通信各个微服务之间.

出于这个原因,必须记录 API,以方便用户正确使用它。为了标准化 REST API 的描述方式,一个由具有前瞻性的行业专家组成的联盟创建了 OpenAPI Initiative (OAI)。

OAI 的目的是创建和发展一种供应商中立/标准的描述格式。

SmartBear 公司将 Swagger 规范捐赠给了 OAI,以将其作为这个开放规范的起点。出于这个原因,Swagger 可以被认为是 API 文档的事实上的标准。

Spring 没有生成 API 文档的内置机制,但是像往常一样,开源社区通过 SpringFox 等框架使此操作非常容易。

SpringFox 使开发人员能够自动为使用 Spring 框架编写的 JSON API 创建可读的规范。

正如我之前提到的,SpringFox 不是 Spring 框架的一个组件——因此它没有得到 Spring 框架贡献者的认可。

我们将配置我们的简单微服务实现,以便记录我们的 API 并构建一个简单的门户,该门户不仅显示 API 文档,而且还提供给用户进行测试。

集成 Swagger 和 SpringFox 所需的第一个操作是在 Maven pom.xml 文件中设置相关依赖项,如下所示:

<dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger2</artifactId>
     <version>2.9.2</version>
</dependency>
<dependency>
     <groupId>io.springfox</groupId>
     <artifactId>springfox-swagger-ui</artifactId>
     <version>2.9.2</version>
</dependency>

然后我们将在 Spring Boot 配置中创建一个 Docket bean,以将 Swagger 2 集成到我们的应用程序中。

这样,SpringFox 提供了主要的 API 配置,具有合理的默认值和方便的配置方法。

以下代码显示了 bean 的实现:

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurationSupport {
    @Bean
    public Docket api() {
       return new Docket(DocumentationType.SWAGGER_2).select()
           .apis(RequestHandlerSelectors.basePackage(                                            
               "com.packtpub.springboot.footballplayermicroservice.controller"))
           .paths(PathSelectors.any())
           .build();
    }
    
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
            .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

我们使用 @EnableSwagger2 注释在类中启用 Swagger 支持。该类的关键点是 api 方法,它使用字符串谓词过滤记录的控制器和方法。

RequestHandlerSelectors.basePackage 谓词匹配 com.packtpub.springboot.footballplayermicroservice.controller 基本包,以过滤将记录的 API。对于 API 路径,我没有设置过滤器,所以我决定公开所有 API。

现在您可以使用以下命令运行应用程序:

$ mvn spring-boot:run

您应该能够通过启动应用程序并将浏览器指向 http://localhost:8080/v2/api-docs 来测试配置。

这样,您可以检索 API 文档的 JSON 表示形式。

以下代码片段显示了您将在浏览器中看到的部分内容:

{
    "swagger":"2.0",
        "info":{
            "description":"Api Documentation",
            "version":"1.0",
            "title":"Api Documentation",
            "termsOfService":"urn:tos",
            "contact":{
            },
            "license":{
                "name":"Apache 2.0",
                 "url":"http://www.apache.org/licenses/LICENSE-2.0"
            }
         },
         "host":"localhost:8080",
         "basePath":"/",
         "tags":[
         {
             "name":"football-player-rest-controller",
             "description":"Football Player REST Controller"
         }
         ],
         "paths":{
             "/footballplayer":{
                 "get":{
                     "tags":[
                         "football-player-rest-controller"
                     ],
                     "summary":"findAll",
                     "operationId":"findAllUsingGET",
                     "produces":[
                         "application/json"
                     ],
                     "responses":{
                         "200":{
                             "description":"OK",
                             "schema":{
                                 "$ref":"#/definitions/Iterable«FootballPlayer»"
                             }
                         },
                         "401":{
                             "description":"Unauthorized"
                         },
                         "403":{
                             "description":"Forbidden"
                         },
                         "404":{
                             "description":"Not Found"
                         }
                     },
                     "deprecated":false
                 }
             },
             "/footballplayer/delete/{id}":{
             "delete":{
                 "tags":[
                     "football-player-rest-controller"
                 ],
                 "summary":"delete",
                 "operationId":"deleteUsingDELETE",
                 "produces":[
                     "application/json"
                 ],
                 "parameters":[
                 {
                     "name":"id",
                     "in":"path",
                     "description":"id",
                     "required":true,
                     "type":"integer",
                     "format":"int32"
                 }
                 ],
                 "responses":{
                     "200":{
                         "description":"OK"
                     },
                     "204":{
                         "description":"No Content"
                     },
                     "401":{
                         "description":"Unauthorized"
                     },
                     "403":{
                         "description":"Forbidden"
                     }
                 },
                 "deprecated":false
             }
         },
         ....

为了获得可读的、结构化的文档,我们将使用 Swagger UI,用户可以在其中查看我们 API 的完整文档并对其进行测试。

如果您调用 http://localhost:8080/swagger-ui.html URL,您将看到 Swagger UI 呈现的生成文档,如下所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

如您所见,Swagger 2 使用驼峰命名约定生成了与 football-player-rest-controller 相关的文档,以检索我们类中使用的单词,以及获取控制器使用的 Models

在我们开始使用新建的微服务之前,我们先自定义一下控制台的header部分,为了说明向我们的用户介绍 UI 中描述的 API 文档类型。

我们将 metadata API 信息添加到 SwaggerConfig 类中,如下所示:

private ApiInfo metaData() {
    return new ApiInfoBuilder()
        .title("Spring Boot REST API")
        .description("\"Spring Boot REST API for Football Player Microservice\"")
        .version("1.0.0")
        .license("Apache License Version 2.0")
        .licenseUrl("https://www.apache.org/licenses/LICENSE-2.0\"")
        .contact(new Contact("Mauro Vocale",
         "https://github.com/Hands-on-MSA-JakartaEE",
         "[email protected]"))
         .build();
 }

请记住,还要在 SwaggerConfig 类的 api 方法中将 metaData 方法的调用添加到管道构建器中。

现在再次运行应用程序,如下所示:

$ mvn spring-boot:run

您现在将看到与构建和公开 API 的组织相关的更多详细信息:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

现在是时候自定义我们的控制器类了。目标是描述我们操作端点的目的。我们将使用 @ApiOperation 注释来描述端点及其响应类型,如下所示:

...
@ApiOperation(value = "View all available football players", response
= Iterable.class)
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Successfully retrieved list"),
@ApiResponse(code = 404, message = "The resource you were trying to reach is not found")
}
)
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public Iterable<FootballPlayer> findAll() {
     return service.findAll();
}

@ApiOperation(value = "Add a football player")
@RequestMapping(value = "/save", method = RequestMethod.POST, produces = "application/json")
public FootballPlayer save(@RequestBody FootballPlayer entity) {
     return service.save(entity);
}

@ApiOperation(value = "Update a football player")
@RequestMapping(value = "/update/{id}", method = RequestMethod.PUT, produces = "application/json")
public FootballPlayer edit(@PathVariable Integer id, @RequestBody FootballPlayer entity) {
     return service.save(entity);
}

@ApiOperation(value = "Delete a football player")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.DELETE, produces = "application/json")
public void delete(@PathVariable Integer id) {
     service.deleteById(id);
}

@ApiOperation(value = "Search a football player with an ID", response = FootballPlayer.class)
@RequestMapping(value = "/show/{id}", method = RequestMethod.GET, produces = "application/json")
public Optional<FootballPlayer> findById(@PathVariable Integer id) {
     return service.findById(id);
}
...

在浏览器中,操作端点的输出将如下所示:

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

现在用户对我们的微服务应用程序公开了哪些 API 以及每个 API 的目标有了一个很好的了解。

现在让我们开始测试其中之一。我们将扩展 Search a football player with an ID API,它的路径如下:/footballplayer/show/{id}

读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务

如您所见,Swagger 向您展示了以下内容:

  • The HTTP verb of the API, which is GET.
  • A brief description of the API.
  • The parameter needed to invoke the API (in our case, Id).
  • The different types of response:
    • A JSON object representation
    • HTTP error codes

任何想要使用我们 API 的用户都可以开始开发代码以与之交互,因为他们拥有这样做所需的所有元素。

要测试 API,用户只需要执行以下操作:

  1. Click on the Try it out button, as follows:
读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务
  1. Insert the ID parameter and click on the Execute button:
读书笔记《hands-on-cloud-native-microservices-with-jakarta-ee》使用Spring Boot 2构建微服务
  1. Finally, the user will see the response of the API call: