Spring Boot 响应式 WebFlux 笔记1

本文介绍了响应式编程模型,重点讲解了Reactor框架下的Flux和Mono,以及如何在SpringWebFlux中使用AnnotatedController和函数式编程实现RESTfulAPI。通过实例展示了如何配置依赖、创建接口并实现用户操作的CRUD功能。
摘要由CSDN通过智能技术生成

1,响应式编程

1.1 快速理解

在现在主流的编程模型中,请求是被同步阻塞处理完成,返回结果给前端。
在响应式的编程模型中,请求是被作为一个事件丢到线程池中执行,等到执行完毕,异步回调结果给主线程,最后返回给前端。
通过这样的方式,主线程(实际是多个,这里只是方便描述哈)不断接收请求,不负责直接同步阻塞处理,从而避免自身被阻塞。

1.2 Reactor 框架

在 Java 生态中,提供响应式编程的框架主要有 Reactor、RxJava、JDK9 Flow API 。
Reactor 说是一个响应式编程框架,又快又不占用内存的那种。

Reactor 有两个非常重要的基本概念:

Flux ,表示的是包含 0 到 N 个元素的异步序列。当消息通知产生时,订阅者(Subscriber)中对应的方法 #onNext(t),#onComplete(t) 和 #onError(t) 会被调用。
Mono 表示的是包含 0 或者 1 个元素的异步序列。
该序列中同样可以包含与 Flux 相同的三种类型的消息通知。

同时,Flux 和 Mono 之间可以进行转换。
例如: 对一个 Flux 序列进行计数操作,得到的结果是一个 Mono 对象。
把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。
其实,可以先暂时简单把 Mono 理解成 Object ,Flux 理解成 List 。

1.3 Spring WebFlux

Spring 官方文档对 Spring WebFlux 介绍总结:

Spring Framework 5 提供了一个新的 spring-webflux 模块。该模块包含了:

  • 对响应式支持的 HTTP 和 WebSocket 客户端。
  • 对响应式支持的 Web 服务器,包括 Rest API、HTML 浏览器、WebSocket 等交互方式。

在服务端方面,WebFlux 提供了 2 种使用方式:

  • 方式一,基于 Annotated Controller 方式实现:基于 @Controller 和 SpringMVC使用的其它注解。也就是说,我们大体上可以像使用 SpringMVC 的方式,使用 WebFlux 。
  • 方式二,基于函数式编程方式:函数式,Java 8 lambda 表达式风格的路由和处理。

WebFlux 可以运行在:

  • 支持 Servlet 3.1 非阻塞 IO API 的 Servlet 容器上
  • 也可以运行在支持异步运行时的,例如说 Netty 或者 Undertow 上

每一个运行时(runtime)适用于将响应式的 ServerHttpRequest 和 ServerHttpResponse 中 request 和 response 的 body 暴露成 Flux 对象,而不是 InputStream 和InputStream 对象,可用于响应式中的背压(backpressure)。
简单来说:

  • 对于 Servlet 来说, ServletRequest#getInputStream() 方法,获得请求的主体内容返回的是InputStream 对象。
  • 对于 WebFlux 来说,ServerHttpRequest#getBody()方法,获得请求的主体内容返回的是 Flux 对象。

2 快速入门

本小节,我们会使用 spring-boot-starter-webflux 实现 WebFlux 的自动化配置。然后实现用户的增删改查接口。接口列表如下:

请求方法URL功能
GET/users/list查询用户列表
GET/users/get获得指定用户编号的用户
POST/users/add添加用户
POST/users/update更新指定用户编号的用户
POST/users/delete删除指定用户编号的用户

2.1 引入依赖

在 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">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>lab-27-webflux-01</artifactId>

    <dependencies>
        <!-- 实现对 Spring WebFlux 的自动化配置 -->
        <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>

    </dependencies>

</project>

我们使用 IDEA Maven 插件 ,查看下== spring-boot-starter-webflux ==依赖中,所引入的依赖。如下图所示:
在这里插入图片描述

  • 引入 reactor-core 依赖,使用 Reactor 作为 WebFlux 的响应式框架的基础。
  • 引入 spring-boot-starter-reactor-netty 依赖,使用 Netty 构建 WebFlux 的 Web 服务器。其中 RxNetty 库,是基于 Reactor 的响应式框架的基础之上,提供出 Netty 的响应式 API 。

为什么 WebFlux 运行在 Servlet 容器上时,需要 Servlet 3.1+ 以上的容器呢?在 Servlet 3.1 规范发布时,它定义了非常重要的特性,Non-blocking I/O 非阻塞 IO ,提供了异步处理请求的支持。我们来详细展开下:

  • 在 Servlet 3.1 规范之前的版本,请求是只能被 Servlet 同步阻塞处理完成,返回结果给前端。
  • 在 Servlet 3.1 规范开始的版本,请求是允许被 Servlet 丢到线程池中处执行,等到执行完毕,异步回调结果给 Servlet ,最后返回给前端。

可以看看 《Servlet 3.0/3.1 中的异步处理》 文章,可以对 WebFlux 有更好的理解。

2.2 基于 Annotated Controller 方式实现

cn.iocoder.springboot.lab27.springwebflux.controller 包路径下,创建 UserController 类。代码如下:

/ UserController.java

@RestController
@RequestMapping("/users")
public class UserController {

    /**
     * 查询用户列表
     *
     * @return 用户列表
     */
    @GetMapping("/list")
    public Flux<UserVO> list() {
        // 查询列表
        List<UserVO> result = new ArrayList<>();
        result.add(new UserVO().setId(1).setUsername("yudaoyuanma"));
        result.add(new UserVO().setId(2).setUsername("woshiyutou"));
        result.add(new UserVO().setId(3).setUsername("chifanshuijiao"));
        // 返回列表
        return Flux.fromIterable(result);
    }

    /**
     * 获得指定用户编号的用户
     *
     * @param id 用户编号
     * @return 用户
     */
    @GetMapping("/get")
    public Mono<UserVO> get(@RequestParam("id") Integer id) {
        // 查询用户
        UserVO user = new UserVO().setId(id).setUsername("username:" + id);
        // 返回
        return Mono.just(user);
    }

    /**
     * 添加用户
     *
     * @param addDTO 添加用户信息 DTO
     * @return 添加成功的用户编号
     */
    @PostMapping("add")
    public Mono<Integer> add(@RequestBody Publisher<UserAddDTO> addDTO) {
        // 插入用户记录,返回编号
        Integer returnId = 1;
        // 返回用户编号
        return Mono.just(returnId);
    }

    /**
     * 更新指定用户编号的用户
     *
     * @param updateDTO 更新用户信息 DTO
     * @return 是否修改成功
     */
    @PostMapping("/update")
    public Mono<Boolean> update(@RequestBody Publisher<UserUpdateDTO> updateDTO) {
        // 更新用户记录
        Boolean success = true;
        // 返回更新是否成功
        return Mono.just(success);
    }

    /**
     * 删除指定用户编号的用户
     *
     * @param id 用户编号
     * @return 是否删除成功
     */
    @PostMapping("/delete") // URL 修改成 /delete ,RequestMethod 改成 DELETE
    public Mono<Boolean> delete(@RequestParam("id") Integer id) {
        // 删除用户记录
        Boolean success = false;
        // 返回是否更新成功
        return Mono.just(success);
    }

}

list() 方法,我们最终调用 Flux#fromIterable(Iterable<? extends T> it) 方法,将 List 包装成 Flux 对象返回。
get(Integer id) 方法,我们最终调用 Mono#just(T data) 方法,将 UserVO 包装成 Mono 对象返回。

add(Publisher addDTO) 方法,参数为 Publisher 类型,泛型为 UserAddDTO 类型,并且添加了 @RequestBody 注解,从 request 的 Body
中读取参数。注意,此时提交参数需要使用 “application/json” 等 Content-Type 内容类型。

add(…) 方法,也可以使用 application/x-www-form-urlencoded 或 multipart/form-data 这两个 Content-Type 内容类型,通过 request 的 Form Data 或
Multipart Data 传递参数。代码如下:

// UserController.java

/**
 * 添加用户
 *
 * @param addDTO 添加用户信息 DTO
 * @return 添加成功的用户编号
 */
@PostMapping("add2")
public Mono<Integer> add(Mono<UserAddDTO> addDTO) {
    // 插入用户记录,返回编号
    Integer returnId = UUID.randomUUID().hashCode();
    // 返回用户编号
    return Mono.just(returnId);
}

此时,参数为 Mono 类型,泛型为 UserAddDTO 类型。
当然,我们也可以直接使用参数为 UserAddDTO 类型。如果后续需要使用到 Reactor API ,则我们自己主动调用 Mono#just(T data) 方法,封装出 Publisher 对象。注意,Flux 和 Mono 都实现了 Publisher 接口。

2.3 基于函数式编程方式

在== cn.iocoder.springboot.lab27.springwebflux.controller== 包路径下,创建 UserRouter 类。代码如下:

// UserRouter.java

@Configuration
public class UserRouter {

    @Bean
    public RouterFunction<ServerResponse> userListRouterFunction() {
        return RouterFunctions.route(RequestPredicates.GET("/users2/list"),
                new HandlerFunction<ServerResponse>() {

                    @Override
                    public Mono<ServerResponse> handle(ServerRequest request) {
                        // 查询列表
                        List<UserVO> result = new ArrayList<>();
                        result.add(new UserVO().setId(1).setUsername("yudaoyuanma"));
                        result.add(new UserVO().setId(2).setUsername("woshiyutou"));
                        result.add(new UserVO().setId(3).setUsername("chifanshuijiao"));
                        // 返回列表
                        return ServerResponse.ok().bodyValue(result);
                    }

                });
    }

    @Bean
    public RouterFunction<ServerResponse> userGetRouterFunction() {
        return RouterFunctions.route(RequestPredicates.GET("/users2/get"),
                new HandlerFunction<ServerResponse>() {

                    @Override
                    public Mono<ServerResponse> handle(ServerRequest request) {
                        // 获得编号
                        Integer id = request.queryParam("id")
                                .map(s -> StringUtils.isEmpty(s) ? null : Integer.valueOf(s)).get();
                        // 查询用户
                        UserVO user = new UserVO().setId(id).setUsername(UUID.randomUUID().toString());
                        // 返回列表
                        return ServerResponse.ok().bodyValue(user);
                    }

                });
    }

    @Bean
    public RouterFunction<ServerResponse> demoRouterFunction() {
        return route(GET("/users2/demo"), request -> ok().bodyValue("demo"));
    }

}
  • 在类上,添加== @Configuration== 注解,保证该类中的 Bean 们,都被扫描到。

  • 在每个方法中,我们都通弄 RouterFunctions#route(RequestPredicate predicate, HandlerFunction handlerFunction) 方法,定义了一条路由。

    • 第一个参数 predicate 参数,是 RequestPredicate 类型,请求谓语,用于匹配请求。可以通过 RequestPredicates 来构建各种条件。
    • 第二个参数 handlerFunction 参数,是 RouterFunction 类型,处理器函数。
  • 采用第三个方法的写法,更加简洁。注意,需要使用 static import 静态引入,代码如下

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.*;
import static org.springframework.web.reactive.function.server.ServerResponse.*;

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值