响应式编程实战(08)-WebFlux,使用注解编程模式构建异步非阻塞服务

news2024/12/23 23:39:40

1 引言

明确了 Spring 家族中 WebFlux 组件诞生的背景和意义。作为一款新型的 Web 服务开发组件:

  • 充分考虑了与原有 Spring MVC 在开发模式上的兼容性,开发人员仍然可以使用基于注解的编程方式来创建响应式 Web 服务
  • WebFlux 也引入了基于函数式编程的全新开发模式

先关注基于注解的编程模型。

2 引入 Spring WebFlux

如果你是第一次创建 WebFlux 应用,最简单使用 Spring 所提供的 Spring Initializer 初始化模板。直接访问 Spring Initializer 网站(http://start.spring.io/),选择创建一个 Maven 项目并指定相应的配置项,然后在添加的依赖中选择 Spring Reactive Web,我们就可以获取一个可运行的 WebFlux 模版项目。

Drawing 0.png

使用 Spring Initializer 初始化响应式 Web 应用示意图。

模板项目中的 pom 文件

<dependencies>      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>       
</dependencies>
  • 最核心的就是 spring-boot-starter-webflux,构成响应式 Web 应用程序开发的基础
  • spring-boot-starter-test 是包含 JUnit、Spring Boot Test、Mockit 等常见测试工具类在内的测试组件库
  • reactor-test 则是用来测试 Reactor 框架的测试组件库

当然,你也可以新建一个任意的 Maven 项目,然后添加这些依赖。这样,使用 Spring WebFlux 构建响应式 Web 服务的初始化环境就准备好了。

3 使用注解编程模型创建响应式 RESTful 服务

  • 基于 Java 注解的方式,这种编程模型与传统的 Spring MVC 一致
  • 使用函数式编程模型

先介绍第一种实现方式。

3.1 RESTful 服务与传统创建方法

在创建响应式 Web 服务之前,我们先来回顾一下传统 RESTful 服务的创建方法。

REST(Representational State Transfer,表述性状态转移)本质只是一种架构风格而非规范。这种架构风格把位于服务器端的访问入口看作是一种资源,每个资源都使用一个 URI 来表示唯一的访问地址。而在请求过程上使用的就是标准的 HTTP 方法,如GET、PUT、POST 和 DELETE。

使用 Spring Boot 来构建一个传统的 RESTful 服务,创建一个 Bootstrap 启动类。Bootstrap 类结构简单且比较固化:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class HelloApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}

@SpringBootApplication 注解的类就是整个应用程序的入口类,通过这个类相当于启动了 Spring 容器。一旦 Spring 容器已经启动,我们就可以通过提供一系列的 Controller 类来构建 HTTP 端点,最简单的 Controller 类如下所示。

@RestController
public class HelloController {
 
    @GetMapping("/")
    public String hello() {
        return "Hello World!";
    }
}

@RestController 注解继承自 Spring MVC 中的 @Controller 注解。与传统的 @Controller 注解相比,@RestController 注解内置基于 JSON 的序列化/反序列化方式,专门用于构建轻量级的 RESTful 端点。通过这个特性,我们在构建 RESTful 服务时可以使用 @RestController 注解来代替 @Controller 注解以简化开发。

@GetMapping 注解也与 Spring MVC 中的 @RequestMapping 注解类似。Spring Boot 2 中引入一批新注解,@PutMapping、@PostMapping、@DeleteMapping 等注解,方便开发人员显式指定 HTTP 的请求方法。当然,你也可以继续使用原先的 @RequestMapping 实现同样的效果。

典型的 Controller

实现了一个根据订单编号 OrderNumber 获取订单信息的 HTTP 端点。这个端点的访问 URI 为“orders/{orderNumber}”,由根路径“orders”+子路径“/{orderNumber}”构成,还指定了对应的 HTTP 请求方法和所需传入的参数:

@RestController
@RequestMapping(value="orders")
public class OrderController {
  
    @Autowired
    private OrderService orderService;       
    
    @GetMapping(value = "/{orderNumber}")
    public Order getOrderByOrderNumber(@PathVariable String orderNumber) {   
        Order order = orderService.getOrderByOrderNumber(orderNumber);
        
        return order;
    }
}

基于注解编程模型来创建响应式 RESTful 服务与使用传统的 Spring MVC 非常类似,通过掌握响应式编程的基本概念和技巧,在 WebFlux 应用中使用这种编程模型几乎没有任何学习成本。

3.2 通过注解构建响应式 RESTful 服务

针对前面介绍的两个 RESTful 服务示例,展示如何就响应式编程模型给出它们的响应式版本。

第一个响应式 RESTful 服务来自对原有 HelloController 示例响应式改造,

改造后:

@RestController
public class HelloController {
 
    @GetMapping("/")
    public Mono<String> hello() {
        return Mono.just("Hello World!");
    }
}

hello() 方法的返回值从普通的 String 对象转化为一个 Mono 对象。

Spring WebFlux 与 Spring MVC 的不同之处在于,前者使用的类型都是 Reactor 中提供的 Flux 和 Mono 对象,而非 POJO。

第一个响应式 RESTful 服务非常简单,在接下来的内容中,我们将更进一步,构建带有一个 Service 层实现的响应式 RESTful 服务。而 Service 层中一般都会使用具体的数据访问层来实现数据操作,但因为响应式数据访问是一个独立的话题,所以我会在后续的“14 | 响应式全栈:响应式编程能为数据访问过程带来什么样的变化?”中进行展开。

这一讲我们还是尽量屏蔽响应式数据访问所带来的复杂性,数据层采用打桩(Stub)的方式来实现这个 Service 层组件。我们将针对常见的订单服务构建一个桩服务 StubOrderService,如下所示。

@Service
public class StubOrderService {
 
    private final Map<String, Order> orders = new ConcurrentHashMap<>();
 
    public Flux<Order> getOrders() {
        return Flux.fromIterable(this.orders.values());
    }
 
    public Flux<Order> getOrdersByIds(final Flux<String> ids) {
        return ids.flatMap(id -> Mono.justOrEmpty(this.orders.get(id)));
    }
 
    public Mono<Order> getOrdersById(final String id) {
        return Mono.justOrEmpty(this.orders.get(id));
    }
 
    public Mono<Void> createOrUpdateOrder(final Mono<Order> productMono) {
        return productMono.doOnNext(product -> {
            orders.put(product.getId(), product);
        }).thenEmpty(Mono.empty());
    }
 
    public Mono<Order> deleteOrder(final String id) {
        return Mono.justOrEmpty(this.orders.remove(id));
    }
}

StubOrderService 用来对 Order 数据进行基本 CRUD 操作。我们使用一个位于内存中的 ConcurrentHashMap 对象来保存所有的 Order 对象信息,从而提供一种桩代码实现方案。

这里的 getOrdersByIds() 方法具有代表性,它接收 Flux 类型的参数 ids。Flux 类型的参数代表有多个对象需要处理,这里使用“07 | Reactor 操作符(上):如何快速转换响应式流?”中所介绍的 flatMap 操作符来对传入的每个 id 进行处理,这也是 flatMap 操作符的一种非常典型的用法。

另外 createOrUpdateOrder() 方法使用 Mono.doOnNext() 方法,将 Mono 对象转换为普通 POJO 对象并进行保存。doOnNext() 方法相当于在响应式流每次发送 onNext 通知时,为消息添加了定制化的处理。

有了桩服务 StubOrderService,我们就可以创建 StubOrderController 来构建具体的响应式 RESTful 服务,它使用 StubOrderService 来完成具体的端点。

StubOrderController 暴露端点都很简单,只是把具体功能代理给 StubOrderService 对应方法:

@RestController
@RequestMapping("/orders")
public class StubOrderController {
 
    @Autowired
    private StubOrderService orderService;
 
    @GetMapping("")
    public Flux<Order> getOrders() {
        return this.orderService.getOrders();
    }
 
    @GetMapping("/{id}")
    public Mono<Order> getOrderById(@PathVariable("id") final String id) {
        return this.orderService.getOrderById(id);
    }
 
    @PostMapping("")
    public Mono<Void> createOrder(@RequestBody final Mono<Order> order) {
        return this.orderService.createOrUpdateOrder(order);
    }
 
    @DeleteMapping("/{id}")
    public Mono<Order> delete(@PathVariable("id") final String id) {
        return this.orderService.deleteOrder(id);
    }
}

WebFlux 中支持使用与 Spring MVC 相同的注解,主要区别在底层通信方式是否阻塞:

  • 简单场景,这两者之间并没有什么太大差别
  • 复杂应用,响应式编程和背压的优势就会体现出来,可以带来整体性能的提升

4 案例集成:ReactiveSpringCSS 中的 Web 服务

作为客服系统,核心业务流程是生成客服工单,而工单的生成通常需要使用到用户账户信息和所关联的订单信息。

案例包含三个独立的 Web 服务:

  • 管理订单的 order-service

  • 管理用户账户的 account-service

  • 核心的客服服务 customer-service

服务之间的交互方式:

ReactiveSpringCSS 案例系统中三个服务的交互方式图

通过这个交互图,已能梳理工单生成的核心流程的伪代码:

generateCustomerTicket {
 
  创建 CustomerTicket 对象
 
	从远程 account-service 中获取 Account 对象
 
	从远程 order-service 中获取 Order 对象
 
	设置 CustomerTicket 对象属性
 
	保存 CustomerTicket 对象并返回
}
  • 【从远程 account-service 中获取 Account 对象】
  • 【从远程 order-service 中获取 Order 对象】

都涉及远程 Web 服务的访问。

先要分别在 account-service 和 order-service 服务中创建对应的 HTTP 端点。

先基于注解编程模型给出 account-service 中 AccountController 的实现过程,完整的 AccountController:

@RestController
@RequestMapping(value = "accounts")
public class AccountController {
 
    @Autowired
    private AccountService accountService;
 
    @GetMapping(value = "/{accountId}")
    public Mono<Account> getAccountById(@PathVariable("accountId") String accountId) {
 
        Mono<Account> account = accountService.getAccountById(accountId);
        return account;
    }
 
    @GetMapping(value = "accountname/{accountName}")
    public Mono<Account> getAccountByAccountName(@PathVariable("accountName") String accountName) {
 
        Mono<Account> account = accountService.getAccountByAccountName(accountName);
        return account;
    }
 
    @PostMapping(value = "/")
    public Mono<Void> addAccount(@RequestBody Mono<Account> account) {
        
        return accountService.addAccount(account);
    }
 
    @PutMapping(value = "/")
    public Mono<Void> updateAccount(@RequestBody Mono<Account> account) {
        
        return accountService.updateAccount(account);
    }
}

这里的几个 HTTP 端点都比较简单,基本都是基于 AccountService 完成的 CRUD 操作。需要注意的是,在 addAccount 和 updateAccount 这两个方法中,输入的参数都是一个 Mono 对象,而不是 Account 对象,这意味着 AccountController 将以响应式流的方式处理来自客户端的请求。

总结

从今天开始,我们将引入 Spring WebFlux 来构建响应式的 RESTful Web 服务。作为一款全新的开发框架,WebFlux 具有广泛的应用场景,同时也支持两种不同的开发模型。本讲针对注解编程模型给出了 RESTful 服务的开发方法。

FAQ

使用 Spring WebFlux 和 Spring MVC 开发 RESTful 服务有什么联系和区别?

使用 Spring WebFlux 和 Spring MVC 开发 RESTful 服务都是基于 Spring 框架的,它们有以下联系和区别:

联系:

  1. 都可以用于开发 RESTful 服务,支持 HTTP 协议的 GET、POST、PUT、DELETE 等请求方式。
  2. 都可以使用 Spring 提供的注解来简化开发,如 @RequestMapping、@GetMapping、@PostMapping 等。
  3. 都可以使用 Spring 提供的拦截器来处理请求前、请求后的逻辑。

区别:

  1. 编程模型不同:Spring WebFlux 基于响应式编程模型,使用 Reactor 库来处理异步和非阻塞的 I/O 操作,而 Spring MVC 则是基于传统的 Servlet API,使用阻塞式 I/O 操作。
  2. 线程模型不同:Spring WebFlux 使用少量的线程来处理大量的并发请求,通过 Reactor 库提供的事件循环机制来实现非阻塞式 I/O 操作。而 Spring MVC 则是使用线程池来处理请求,并且每个请求都会占用一个线程。
  3. 响应式支持不同:Spring WebFlux 支持响应式编程,可以使用 Mono 和 Flux 类型来处理异步操作和流式数据。而 Spring MVC 则不支持响应式编程。
  4. 异常处理不同:Spring WebFlux 中的异常处理机制不同于 Spring MVC,它使用函数式编程模型来处理异常。在 WebFlux 中,异常处理器是一个函数,它接收一个 ServerRequest 对象和一个 Throwable 对象,并返回一个 Mono 对象。而在 Spring MVC 中,异常处理器是一个类,需要实现 HandlerExceptionResolver 接口。
  5. 性能和并发性不同:由于 Spring WebFlux 使用少量的线程来处理大量的并发请求,因此它可以更好地保护系统免受拒绝服务攻击。而 Spring MVC 则需要使用线程池来处理请求,容易受到拒绝服务攻击的影响。

总之,选择使用 Spring WebFlux 还是 Spring MVC 取决于具体的应用场景和需求。如果需要处理大量的并发请求,并希望使用响应式编程模型来实现高性能和高并发,可以选择 Spring WebFlux;如果应用场景相对简单,可以选择 Spring MVC。

下一文会继续讨论 Spring WebFlux 的应用,我们将分析全新的函数式编程模型中的编程组件,并完成与 ReactiveSpringCSS 的集成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/737244.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux下有名管道mkfifo使用

Linux下实现进程通信的方式有很多种&#xff0c;今天要说的是有名管道&#xff0c;有名管道比命名管道的优势是可以在任何进程之间传递数据。有名管道通信是依赖于管道文件这种特殊类型文件来进行的。 目录 1.mkfifo命令 2.mkfifo库函数 1.mkfifo命令 mkfifo命令可以创建管…

HuilderX 运行到 MUMU模拟器

1.网易官网下载MuMu模拟器&#xff0c;一定要打开MuMu模拟器&#xff1b; MuMu模拟器官方下载https://mumu.163.com/ 2.到MUMU模拟器的安装目录&#xff0c;找到adb.exe在的目录下&#xff0c;复制其路径&#xff1b; 举例 &#xff1a;D:/Program Files/MuMuPlayer-12.0/sh…

CSPM(项目管理专业人员能力评价)和软考有什么区别?

一、国标项目管理&#xff08;项目管理专业人员能力评级&#xff09;证书是什么&#xff1f; 《项目管理专业人员能力评价要求》&#xff08;GB/T 41831-2022&#xff09;是2022年10月12日开始实施的一项中国国家标准&#xff0c;归口于全国项目管理标准化技术委员会。 《项目…

一种环肽52661-98-0,cyclo(Gly-Ser),环(甘氨酰-L-丝氨酰),氨基酸中间体

资料编辑|陕西新研博美生物科技有限公司小编MISSwu cyclo(Gly-Ser)&#xff08;CAS号&#xff1a;52661-98-0&#xff09;一种环肽&#xff0c;一般作为氨基酸中间体&#xff0c;含有甘氨酰和丝氨酰&#xff0c;Ser Serine 丝氨酸&#xff0c;也称β羟基丙氨酸&#xff0c;丝氨…

促进协作、提高生产力:育碧选择Perforce Helix Core的原因

Perforce Helix Core成为育碧&#xff08;Ubisoft&#xff09;的主要源代码控制工具已经超过六年了&#xff0c;被团队中的程序员和美术人员在大部分项目中使用。在育碧蒙特利尔工作室&#xff0c;有超过1,200名的开发人员使用Perforce Helix Core来储存源代码和数字资产&#…

Appium xpath定位

xpath应该是最准确的定位方式&#xff0c;不管你有没有id、class或者其他的元素&#xff0c;uiautomator总是可以识别出xpath&#xff0c;因为手机APP的控件布局类似于HTML的树形结构。 如右图所示 xpath很长&#xff0c;显然不可能人手动来对其进行编写&#xff0c;最好的就是…

算法竞赛备赛之经典基础算法训练提升,暑期集训营培训

目录 1.排序 1.1.快速排序 1.2.归并排序 2.二分 2.1.整数 2.2.浮点数 3.高精度 3.1.高精度加法 3.2.高精度减法 3.3.高精度乘法 3.4.高精度除法 4.前缀和 5.差分 6.双指针算法 7.位运算 8.离散化 8.1.unique函数实现 9.区间合并 1.排序 1.1.快速排序 快速排…

vue 运行时正常,打包却报错

解决方法&#xff1a;删除vue-cli 自带的压缩 plugin&#xff1a;OptimizeCssnanoPlugin 具体操作&#xff1a;找到vue.config.再添加如下删除配置

万万没想到!!号称国内Java八股文天花板(典藏版)首次开源

应届毕业生的第一份工作干多久跳槽比较合适&#xff1f; 都说现在应届毕业生找工作跳槽频繁&#xff0c;而所有用人单位都希望招揽的人才能一直在公司里干下去&#xff0c;但是人各有志&#xff0c;作为劳动者的应届毕业生有自主选择职业的权利&#xff0c;这就造成很多应届生…

今天分享:智能ai绘画软件哪个好

在一个遥远的未来&#xff0c;艺术界经历了一场革命性的变革。艺术家们不再依赖传统的画笔和颜料&#xff0c;而是转向了ai绘画工具&#xff0c;这是一种集人工智能和创造力于一身的技术。在这个世界中&#xff0c;我有幸遇到了一个与众不同的艺术家&#xff0c;他的名字叫亚历…

Hubspot为什么这么牛?国内有哪些类似软件

国外CRM圈内&#xff0c;除了大佬Salesforce外&#xff0c;还有HubSpot、Oracle、SAP等知名CRM公司。其中&#xff0c;HubSpot在国外2023年最佳CRM软件排行榜中名列第四&#xff0c;在最佳免费CRM软件排行榜中名列第二&#xff0c;我们先来看下它到底有多优秀&#xff0c;然后再…

Deffie-Hellman 算法

Deffie-Hellman 算法简介 Deffie-Hellman(简称 DH) 密钥交换是最早的密钥交换算法之一&#xff0c;它使得通信的双方能在非安全的信道中安全的交换密钥&#xff0c;用于加密后续的通信消息。 Whitfield Diffie 和 Martin Hellman 于 1976 提出该算法&#xff0c;之后被应用于安…

指令周期的数据流

5.2 指令周期的数据流 指令周期 机器周期/CPU周期 CPU时钟周期/节拍 取指周期 间址周期 执行周期 中断周期 标志触发器FE IND EX INT 数据流 取指周期 根据PC中的内容取出指令代码并存放在IR中 间址周…

Acrelcloud-9500 智能电瓶车充电桩收费云平台

1. 概述 电动车火灾事故频频发生&#xff0c;毫不起眼的电动车屡次引发夺命大火&#xff0c;电动车已然成为火灾“重灾区”。为预防和遏制电动自行车火灾事故发生&#xff0c;三令五申各种政策&#xff0c;为此安委会曾出台《电动自行车集中停放和充电治理方案》。 大部分充电过…

Linux - 进阶 NFS 服务器 工作原理,安装,主文件分析

NFS工作原理 : 示例图 &#xff1a; 我们在上篇文章也讲过&#xff0c; 要实现 NFS 服务的搭建&#xff0c;最起码得 两个 服务 &#xff08; NFS 服务&#xff0c;RPC 服务&#xff09; 涉及 三方 &#xff1a; 服务端 &#xff08; 房源 &#xff09; 客户端 &…

如何将mov转换成mp4?这篇文章教会你如何转换

MP4格式是一种通用的视频格式&#xff0c;几乎所有的播放器都能够支持它&#xff0c;包括电视、智能手机、平板电脑等等。而mov格式则主要被苹果设备所使用&#xff0c;其他设备可能会出现无法播放的情况。由于MP4格式的广泛兼容性&#xff0c;可以更方便地分享视频给其他人观看…

linux入门之进程控制(下)进程程序替换,shell运行原理,手写一个mini-shell

文章目录 一、进程程序替换 1.替换原理 2.替换函数 3.函数解释 4.命名理解 二、手写一个mini Shell 一、进程程序替换 创建子进程的目的就是为了让子进程执行特定的任务&#xff0c;比如&#xff1a;1.让子进程执行父进程的一部分代码&#xff1b;2.让子进程指向一个全新的程序…

java项目之高校二手交易平台(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的高校二手交易平台。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架…

建站教程:阿里云轻量应用服务器搭建网站流程

阿里云轻量应用服务器怎么使用&#xff1f;阿里云百科分享轻量应用服务器从选配、配置建站环境、轻量服务器应用服务器远程连接、开端口到网站上线全流程&#xff1a; 阿里云轻量应用服务器使用教程 轻量应用服务器很火爆因为成本足够低&#xff0c;阿里云2核2G3M带宽轻量服务…

基于Linux下的C语言项目实战--本地账号管理系统

C语言开发项目实战&#xff1a; C语言是一门通用计算机编程语言&#xff0c;广泛应用于底层开发。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。尽管C语言提供了许多低级处理的功能&#xff…