使用 Spring Boot 和 Postgres 构建反应式 RESTful Web智能AI代理服务

使用 Spring Boot 和 Postgres 构建反应式 RESTful Web智能AI代理服务

img

Spring Boot 2 与 Reactor

在本教程中,我们将学习如何使用Spring WebFlux框架以及Spring Data R2DBC来构建、测试和使用简单的反应式 REST 应用程序。

首先,让我们了解以下内容:

  • 为什么要采用反应式编程?
  • 什么是反应式编程?
  • Spring WebFlux 框架
  • 什么是 R2DBC?

为什么要采用反应式编程?

我们生活在一个微服务的世界里。为了构建一个简单的应用程序,我们使用多个微服务,它们相互交互。我们还必须满足可扩展性、资源利用率、延迟等期望。当我们谈论微服务时,Spring MVC 是最常用的行业标准 Web 开发框架之一,用于创建可扩展且可扩展的微服务。

在传统的 Spring MVC 应用程序中,当服务器收到请求时,会创建一个 servlet 线程。它将请求委托给工作线程进行 I/O 操作,例如数据库访问、REST API 调用等。当工作线程忙于执行请求时,servlet 线程(请求线程)继续处于等待状态并被阻塞。这也称为同步请求处理,它在每个请求模型上按线程工作。

img

Spring MVC 请求/响应流

在上面的模型中,并发是通过为每个请求从线程池中创建一个新线程来处理的。但是只能创建有限数量的线程,因为每个线程都消耗单独的内存。我们可以增加线程池大小,但这将消耗额外的内存,如果我们将线程池大小保持在较低水平,那么我们就无法扩展。为了应对这种情况,我们可以增加应用程序的内存,也可以在负载均衡器后面运行应用程序的多个实例。这两种选择都有助于我们扩展,但也会增加成本。因此问题出现了——我们如何用更少的线程来处理更多的请求?反应式编程是我们可以克服上述限制的方法之一。为了解决基于 Spring MVC 的微服务的缺点,我们可以使用基于反应式 Spring 的微服务。

什么是反应式编程?

简单来说,反应式编程是指异步、事件驱动的非阻塞应用程序,并且需要少量线程来处理请求。它围绕发布者-订阅者模式构建。在反应式编程中,我们发起对资源的请求,然后继续执行其他任务。当数据可用时,我们会收到通知以及数据以通知调用者。在回调函数中,我们可以根据应用程序要求处理响应。反应式代码用更少的资源完成更多工作。通过反应式处理,我们可以用更少的微服务实例满足更多并发用户的需求。

现在我们知道了什么是反应式编程以及它如何改进传统的 REST API 设计,我们可以继续将反应式编程与 Spring 一起使用。

Spring WebFlux 框架

Spring WebFlux 是传统 Spring MVC 的替代品。Spring Framework 5 包含一个新spring-webflux模块。Spring WebFlux是一个从头开始构建的非阻塞 Web 框架,可利用多核、下一代处理器并处理大量并发连接。它在内部使用Project Reactor及其发布者实现:FluxMono

它支持两种编程模型:

  • 带注释的控制器:与 Spring MVC 一致,并基于spring-web模块中的相同注释。Spring MVC 和 WebFlux 控制器都支持反应式(Reactor 和 RxJava)返回类型,因此很难区分它们。一个显着的区别是 WebFlux 还支持反应式@RequestBody参数。
  • 功能端点:基于 Lambda 的轻量级函数式编程模型。您可以将其视为应用程序可用于路由和处理请求的小型库或一组实用程序。带注释的控制器的最大区别在于,应用程序负责从头到尾处理请求,而不是通过注释声明意图并回调。

在这里,我们将重点关注基于注释的反应组件模型。

img

反应式请求处理

什么是 R2DBC?

R2DBC 代表反应式关系数据库连接,这是一个使用反应式驱动程序集成关系数据库的孵化器。Spring Data R2DBC 为 R2DBC 提供了熟悉的 Spring 抽象和存储库支持。它帮助 Spring 驱动的应用程序 以反应式方式执行数据库操作。目前,只有PostGres、MSSQL 和 H2支持R2DBC驱动程序**。**

创建响应式 REST 用户管理应用程序

让我们使用 Spring Webflux 框架和Spring Data R2DBC创建一个简单的 Reactive REST 用户管理应用程序。

先决条件

在开始之前,我们必须确保本地机器上安装了以下先决条件:

构建步骤

  1. 设置 Spring WebFlux 项目设置
  2. 包括其他 Maven 依赖项
  3. 提供 Postgres 和 H2 的配置
  4. 定义模型类
  5. 创建存储库
  6. 定义 Rest 控制器端点
  7. 构建服务层
  8. 使用 WebTestClient 和 Swagger-UI 测试应用程序
  9. 通过 Web 客户端使用响应式 API
  10. 结论

1.设置 Spring WebFlux 项目

在本教程中,我们使用 JDK 1.8 和 Spring Boot 2.3.0.RC1 项目*。*使用start.spring.io并使用以下工件创建示例项目:

  • Spring Reactive Web: Spring Reactive Web 为我们的应用程序提供了一个反应特性。
  • **Spring Data R2DBC:**提供反应式关系数据库连接,以便在反应式应用程序中使用 Spring Data 将数据持久保存在 SQL 存储中。
  • Lombok: Java 注释库,有助于减少样板代码。
  • **H2 数据库:**提供支持 JDBC API 和 R2DBC 访问的快速内存数据库,占用空间小(2mb)。它是集成测试所必需的。
  • **PostgreSQL 驱动程序:**一种 JDBC 和 R2DBC 驱动程序,允许 Java 程序使用标准的、独立于数据库的 Java 代码连接到 PostgreSQL 数据库。

img

创建示例项目

2. 添加额外的 Maven 依赖项

将下面显示的附加依赖项添加到我们的项目,以便为我们的应用程序启用 Swagger。

注意:Swagger 3 即将发布,它包含对 netty 的支持。在此之前我们可以使用快照版本。

<依赖性
    > <groupId> io.springfox </ groupId> 
    <artifactId> springfox-swagger2 </ artifactId> <版本> 
    3.0.0-SNAPSHOT </版本> </依赖性> <依赖性>     <groupId> io.springfox </ groupId     > <artifactId> springfox-swagger-ui </ artifactId>     <版本> 3.0.0-SNAPSHOT </版本> < 
/依赖性> <依赖性>     <groupId> io.springfox </ groupId     > <artifactId> springfox-spring-webflux </ artifactId>     <版本> 3.0.0-SNAPSHOT </版本> < /依赖性>

3. 提供配置

3.1 Swagger 配置

为我们的反应式应用程序配置 Swagger,这将使我们能够轻松地测试我们的应用程序。

<iframe src="https://medium.com/media/a28801894e5f9523cf6c75bd7b607181" allowfullscreen="" frameborder="0" height="542" width="680" title="swagger 配置" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 541.997px; left: 0px;"></iframe>

Swagger 配置

3.2 数据库相关配置

我们已经在类路径中添加了反应流 Postgres 驱动程序来执行非阻塞 DB 操作。接下来,使用 application.yml 文件提供与 Postgres 和 H2 相关的某些配置。此外,配置多个Spring 配置文件,以便我们可以根据不同的环境使用不同的配置。我们将使用 Postgres 数据库进行开发/生产,使用 H2 数据库进行测试。

spring:
  配置文件:
    活动:dev 
--- 
spring:
  配置文件:dev 
  r2dbc:
    url:r2dbc:postgresql://localhost:5432/test
    用户名:postgres
    密码:postgres 日志
记录:
  级别:
    org.springframework.data.r2dbc:调试
--- 
spring:
  配置文件:测试
  r2dbc:
    url:r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    名称:sa
    密码:

我们无法将整个 Spring Boot 功能与 Spring Data R2DBC 一起使用。我们需要执行以下步骤来手动添加表:

a) 使用 Spring Data R2DBC,无法在运行时创建表。我们可以从外部创建表,也可以在资源文件夹中创建一个 schema.sql 文件并以编程方式执行它们。对于我们的应用程序,我们将在资源文件夹中创建一个包含所有 DDL 语句的 schema.sql 文件。

如果存在用户,则删除表;
创建表用户(id INT 默认生成为 IDENTITY PRIMARY KEY,名称 VARCHAR(100)NOT NULL,年龄整数,薪水小数);
如果存在部门,则删除表;
创建表部门(id INT 默认生成为 IDENTITY PRIMARY KEY,user_id 整数,名称 VARCHAR(100)NOT NULL,loc VARCHAR(100));

b) 执行schema.sql文件,我们需要重写ConnectionFactoryInitializer Bean。

<iframe src="https://medium.com/media/51d8fbbc35dc597176b0660e1256ca1e" allowfullscreen="" frameborder="0" height="319" width="680" title="自定义连接工厂初始化器" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 318.993px; left: 0px;"></iframe>

自定义连接工厂初始化器

4. 定义模型类

创建两个域对象UserDepartment。User实体具有 ID、Name、Age 和 Salary。我们将使用User实体来测试用户管理应用程序。实体类用“@Table”注释进行注释,以标识要保留在数据库中的域对象。此外,我们还@Id为每个实体添加了定义表的主键。

@Data 
@AllArgsConstructor 
@NoArgsConstructor 
@Table("users") 
public class User { 

    @Id 
    private Integer id; 
    private String name; 
    private int age; 
    private double salary; 
}@Data 
@AllArgsConstructor 
@NoArgsConstructor 
@Table("department")
公共类 Department { 
    @Id
    私有整数 id;
    私有字符串名称; 
    @Column("user_id")
    私有整数 userId;
    私有字符串 loc; 
}

5. 创建存储库

创建一个 JPA UserRepository,我们可以使用它来执行所有与数据库相关的活动。它将用作支持非阻塞反应流的数据存储库。该UserRepository接口进行了扩展ReactiveCrudRepository,例如,提供了基本的CRUD功能。Spring Boot 会在运行时自动插入此接口的实现。

我们添加了一个自定义查询来根据用户年龄获取用户信息。

公共接口 UserRepository 扩展了 ReactiveCrudRepository<User,Long> { 
    @Query("从年龄 >= $1 的用户中选择 *") 
    Flux<User> findByAge(int age); 
}

为 Department 实体创建另一个 JPA 存储库。

公共接口 DepartmentRepository 扩展了 ReactiveCrudRepository<Department,Integer> { 
    Mono<Department> findByUserId(Integer userId); 
}

6. 定义 Rest 控制器端点

最后,让我们编写一些可以暴露给客户端的 API。RestController 发布User反应

<iframe src="https://medium.com/media/1d22b681c6612526ee9afb6a65395021" allowfullscreen="" frameborder="0" height="1550" width="680" title="用户控制器" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 1550px; left: 0px;"></iframe>

用户控制器

所有控制器端点都以FluxMono的形式返回 Publisher 。从上面的代码中我们可以看出,如果我们想向调用者返回单个资源,那么我们需要使用Mono, 如果我们想返回资源集合,那么我们需要使用Flux

7.构建服务层

让我们为我们的应用程序构建服务层。服务层用于封装业务逻辑,也用于集中数据访问。

<iframe src="https://medium.com/media/67c9a9b30736abc8d0d1ea7cf4ec4713" allowfullscreen="" frameborder="0" height="1990" width="680" title="用户服务" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 1990px; left: 0px;"></iframe>

用户服务

在这个例子中,除了使用反应式存储库与 Postgres 数据库交互之外,UserService 没有太多业务逻辑。

但我们也可以看看下面执行并行操作的方法。

7.1 并行调用获取相同类型的数据

公共 Flux<User> fetchUsers(List<Integer> userIds) {
    返回 Flux.fromIterable (userIds)             .parallel()             .runOn(Schedulers.elastic ())             .flatMap(i -> findById(i)) .ordered ((u1, u2 )             -> u2.getId() - u1.getId()); }

在这里,我们尝试同时获取多个用户的详细信息,并将结果作为用户列表返回。从 userIds 列表创建Flux 后,它会调用 parallel方法,该方法在内部创建ParallelFlux —**这表示并行执行。**在这里, 我们决定使用弹性调度程序来运行调用,但我们可以选择任何其他配置。接下来,我们调用flatMap来运行 findById 方法,该方法返回ParallelFlux。最后, 我们需要指定如何将ParallelFlux转换为简单的Flux。因此,我们使用了带有自定义比较器的有序方法。

7.2 并行调用获取不同类型的数据

Mono<User> user = findById(userId).subscribeOn(Schedulers.elastic ( )); 
Mono<Department> Department = getDepartmentByUserId(userId).subscribeOn(Schedulers.elastic ( ));
返回 Mono.zip (user, Department, userDepartmentDTOBiFunction) ;

在某些情况下,我们可能希望合并多个数据库/API 调用的结果并将结果返回给调用者。在这种情况下,Mono类提供了静态zip方法,它允许我们将两个或多个结果组合在一起。由于subscribeOn方法不订阅Mono *,*因此我们使用 Scheduler。同样,我们仅使用弹性调度程序,以确保每个订阅都在专用的单个线程上进行。

8.使用 WebTestClient 和 Swagger-UI 测试应用程序

我们将使用 WebTestClient 对我们的 REST API 执行集成测试。

<iframe src="https://medium.com/media/3198b25c168acfe4fde6617d7c5325ed" allowfullscreen="" frameborder="0" height="3462" width="680" title="用户控制器测试" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 3462px; left: 0px;"></iframe>

用户控制器测试

我们还可以使用 Swagger-UI 来测试我们的应用程序。根据我们的应用程序显示以下结果。

img

Swagger-UI.html

9. 通过 Web 客户端使用响应式 API

到目前为止,我们已经了解了如何公开反应式 API。接下来,我们将了解如何使用反应式 API。在这里,我们将使用 WebClient 与之前创建的反应式 API 进行交互。Spring 5 中引入的WebClient是一个支持反应式流的非阻塞客户端。我们可以创建一个简单的WebClient* 来从我们的用户管理应用程序中检索数据。*

<iframe src="https://medium.com/media/0ccd0fd670df96a4e36aa0d1d8f39811" allowfullscreen="" frameborder="0" height="847" width="680" title="用户客户端" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 846.997px; left: 0px;"></iframe>

用户客户端

10. 结论

um.com/media/0ccd0fd670df96a4e36aa0d1d8f39811" allowfullscreen="" frameborder="0" height="847" width="680" title="用户客户端" class="el n fd dy bg" scrolling="no" style="box-sizing: inherit; top: 0px; width: 680px; height: 846.997px; left: 0px;"></iframe>

用户客户端

10. 结论

我们刚刚学习了使用 Spring 进行响应式编程的基础知识,并使用支持响应式 Web 组件的 Spring WebFlux 框架和 Spring data R2dbc 构建了一个简单的 Restful 服务。我们学习了如何分别使用RestControllerWebClient发布和使用响应式流。我们还学习了如何使用 WebTestClient 对响应式 Rest API 进行集成测试。响应式堆栈 Web 框架 Spring WebFlux 并不是 Spring MVC 模块的替代品。Spring MVC 模块将成为 Spring 生态系统的一部分。

博客原文:专业人工智能技术社区

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值