springwebflux学习记录(代码片段)

AlaGeek AlaGeek     2022-11-29     408

关键词:

本文按照如下顺序一步步深入解释WebFlux是个什么东西:

1、Reactive Stream
2、Reactor
3、WebFlux

其中 Reactive Stream 是 Java 9 新增的一个重要特性,而 Reactor 就相当于 Java 8 的 Stream 流 + Java 9 的 Reactive Stream,最后的 WebFlux 的核心就是基于 Reactor 的相关 API 开发的。


1、Reactive Stream

响应式流是 Java 9 引入的一套基于发布/订阅模式的数据处理规范,它的目标是以非阻塞背压方式实现数据的异步流。

这个是网上摘抄的一个定义,其中背压原先是一个工程里的概念,指的是在管道运输中,气流或液流由于管道突然变细、急弯等原因导致由某处出现了下游向上游的逆向压力,而在响应式流中就被引申为了:当数据流传输过程中,发布者生产数据的速度大于订阅者消费数据的速度时,订阅者告诉发布者暂时不需要更多的数据了这么一个交互动作。实际上就是流控。

Java 9 的响应式流精简为了四个接口:

  • Publisher
  • Subscriber
  • Subscription
  • Processor

其中 Publisher 是发布者接口,Subscriber 是订阅者接口,Subscription 接口用于联系发布者和订阅者,你可以理解为“合同”,两者签订了合同后,才能进行数据流传输,Processor 接口既是发布者又是订阅者,用于中间数据处理,承上启下。

以下是用这四个接口写的一个案例:

public class FlowDemo 

    public static void main(String[] args) throws InterruptedException 

        // 定义发布者
        SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();

        MyProcessor processor = new MyProcessor();

        publisher.subscribe(processor);

        // 定义订阅者
        Flow.Subscriber<String> subscriber = new Flow.Subscriber<>() 

            private Flow.Subscription subscription;

            @Override
            public void onSubscribe(Flow.Subscription subscription) 
                // 保存订阅关系,需要用它来给发布者响应
                this.subscription = subscription;
                // 请求一个数据
                this.subscription.request(1);
            

            @Override
            public void onNext(String item) 
                // 接收数据并处理
                System.out.println("subscriber 接收数据: " + item);
                // 再请求一个数据
                this.subscription.request(1);
                // 达到目标,调用cancel告诉发布者不再接收数据
                //this.subscription.cancel();
            

            @Override
            public void onError(Throwable throwable) 
                // 出现异常
                throwable.printStackTrace();
                // 停止接收数据
                this.subscription.cancel();
            

            @Override
            public void onComplete() 
                System.out.println("subscriber 处理完毕");
            
        ;

        // 发布者和订阅者建立订阅关系
        processor.subscribe(subscriber);

        // 生产数据,并发布
        for (int i = 0, j = 1; i < 4; i++, j *= -1) 
            System.out.println("publisher 生成数据: " + i * j);
            publisher.submit(i * j);
        

        // 关闭发布者
        publisher.close();

        // 主线程延时
        Thread.currentThread().join(10000);

        // 关闭中转
        processor.close();
    



class MyProcessor extends SubmissionPublisher<String> implements Flow.Processor<Integer, String> 

    private Flow.Subscription subscription;

    @Override
    public void onSubscribe(Flow.Subscription subscription) 
        this.subscription = subscription;
        this.subscription.request(1);
    

    @Override
    public void onNext(Integer item) 
        System.out.println("processor 接收数据: " + item);
        item = item < 0 ? -item : item;
        this.submit(item + "(转换后)");
        this.subscription.request(1);
    

    @Override
    public void onError(Throwable throwable) 
        throwable.printStackTrace();
        this.subscription.cancel();
    

    @Override
    public void onComplete() 
        System.out.println("processor 中转完成");
    

这个案例中用 Publisher 的实现类 SubmissionPublisher 定义了一个发布者,用 Subscriber 定义了一个订阅者,其中实现的方法中,onSubscribe 用于和发布者建立联系,onNext 用于接收数据,onError 用于异常处理,onComplete 在发布者关闭后执行,中转者通过继承 SubmissionPublisher 以及实现接口 Processor 来执行发布者和订阅者的职责,如下是案例执行结果:

发布者与中转者建立订阅关系,中转者与订阅者建立订阅关系,发布者将循环生成的数据发送给中转者,中转者将负数全部处理为正数,然后发送给订阅者。

2、Reactor

Reactor与Spring是兄弟项目,侧重于Server端的响应式编程,是一个基于 Java 8 的实现了响应式流规范的响应式库,虽然说是基于 Java 8 实现的,但是简单来说 Reactor 可以理解为是 Java 8 stream + Java 9 reactive stream。

在 Reactor 中发布者由 Flux 和 Mono 两个类定义:一个 Flux 对象代表了一个包含0-N个元素的序列,而一个 Mono 对象代表了一个包含0-1个元素的序列。

代码样例如下:

public class ReactorDemo 

    public static void main(String[] args) 

        // 定义订阅者
        Subscriber<Integer> subscriber = new Subscriber<Integer>() 

            private Subscription subscription;

            @Override
            public void onSubscribe(Subscription subscription) 
                // 保存订阅关系,需要用它来给发布者响应
                this.subscription = subscription;
                // 请求一个数据
                this.subscription.request(1);
            

            @Override
            public void onNext(Integer item) 
                // 接收数据并处理
                System.out.println("接收到数据: " + item);
                // 再请求一个数据
                this.subscription.request(1);
                // 达到目标,调用cancel告诉发布者不再接收数据
                //this.subscription.cancel();
            

            @Override
            public void onError(Throwable throwable) 
                // 出现异常
                throwable.printStackTrace();
                // 停止接收数据
                this.subscription.cancel();
            

            @Override
            public void onComplete() 
                System.out.println("处理完毕");
            
        ;

        String[] strings = "1","2","3";

        Flux.fromArray(strings)
                // jdk8 stream
                .map(Integer::parseInt)
                // jdk9 reactive stream
                .subscribe(subscriber);
    


代码中的订阅者实际是从第一步中的代码拷贝过来的,可以看到两个代码在订阅者的包引入上有所差异,原因是第一步中的案例是基于 Java 9 开发的,而第二步中的案例是基于 Java 8 + Reactor相关jar包 开发的。

代码中首先用 Flux 的 fromArray 方法将字符串数组转化为了一个数据流,然后因为订阅者消费的数据是Integer类型,所以用 map 将 String 转为 Integer,最后调用 subscribe 方法对流进行消费,这个方法的传参可以传入一个订阅者。

样例执行结果如下:

3、Spring WebFlux

讲 Reactor 主要是为了引出 Flux 和 Mono,实际上这部分看懂已经足够学习WebFLux(没看懂可能也能学)。

Spring WebFlux 是随着 Spring5 推出的响应式编程框架,与 Spring MVC 相比,WebFlux 最大的特点就是非阻塞,这部分直接有代码来讲解。

3.1 类MVC开发模式

首先创建一个测试项目,在pom文件中添加如下依赖:

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

然后创建一个controller,写入以下测试代码:

@GetMapping("/1")
public String get1() 
    long start = System.currentTimeMillis();
    log.info("get1 start");
    String str = createStr("1");
    log.info("get1 end");
    long end = System.currentTimeMillis();
    log.info("get1 use time: ", end - start);
    return str;


@GetMapping("/2")
public Mono<String> get2() 
    long start = System.currentTimeMillis();
    log.info("get2 start");
    Mono<String> mono = Mono.fromSupplier(() -> createStr("2"));
    log.info("get2 end");
    long end = System.currentTimeMillis();
    log.info("get2 use time: ", end - start);
    return mono;


private String createStr(String str) 
    try 
        TimeUnit.SECONDS.sleep(5);
     catch (InterruptedException e) 
        e.printStackTrace();
    
    return str;

其中 get1 方法是典型的 MVC 写法,在方法中调用 createStr 方法,为了模拟阻塞,在 createStr 方法中加了5秒延时,而 get2 方法就是 WebFlux 的写法,在方法体中通过语句 Mono.fromSupplier(() -> createStr(“2”)) 来构造一个数据流,这里需要注意的是,流是具有惰性处理的特性的,如果方法体内没有消费这个流,那么实际上 createStr是不会执行的,那么阻塞的5秒也就不存在了。如下是代码执行结果:

可以看到,get2方法确实没有产生延迟,因为它只是返回了一个流,而这个流是在方法体执行完后,被 Spring WebFlux 框架消费的,以运行在 Tomcat 上为例,这样的写法就不会导致 Servlet线程 处于堵塞状态,从而提升吞吐量。

上面展示的是返回值为 Mono 的情况,下面演示种返回值为 Flux 的案例:

@GetMapping(value = "/3", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> flux() 
    long start = System.currentTimeMillis();
    log.info("get3 start");
    Flux<String> stringFlux = Flux.fromStream(IntStream.range(1, 5).mapToObj(
        i -> 
            try 
                TimeUnit.SECONDS.sleep(1);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            return "flux data " + i + "\\n";
        
    ));
    log.info("get3 end");
    long end = System.currentTimeMillis();
    log.info("get3 use time: ", end - start);
    return stringFlux;

Flux 和 Mono最大的区别就在于 Flux 产生的数据流可能会有多个元素,那么对于返回给调用方就有两种方式,一种是所有元素都消费完一起返回,另一种是消费一个返回一个,上述代码中的 MediaType.TEXT_EVENT_STREAM_VALUE 就是告诉浏览器一个个返回,如下是运行结果:
第一次返回:

第四次返回:

同样,方法体内消耗的时间如下:

3.2 Router Function开发模式

上面代码的开发模式其实跟Spring MVC的差别不大,也是写一个controller,里面的方法用@RequestMapping注解,而WebFlux还有一种开发,叫做 Router Function。

对应controller中的方法,Router Function开发模式有 HandlerFunction 类:

Mono<T extends ServerResponse> handle(ServerRequest request);

而请求路由由类 RouterFunction 实现:

Mono<HandlerFunction<T>> route(ServerRequest request);

就比如将上面的 get2 方法改写如下:

@Component
public class TestHandler 

    public Mono<ServerResponse> get2(ServerRequest serverRequest) 
        return ok().contentType(MediaType.TEXT_PLAIN).body(Mono.fromSupplier(() -> createStr("2")), String.class);
    

    private String createStr(String str) 
        try 
            TimeUnit.SECONDS.sleep(5);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        return str;
    



@Configuration
public class RouterConfig 

    @Resource
    private TestHandler testHandler;

    @Bean
    public RouterFunction<ServerResponse> timerRouter() 
        return route(GET("/get2"), testHandler::get2);
    

启动服务后,访问 /get 就会调用 testHandler 的 get2 方法来处理请求。


参考文献:

1、https://blog.csdn.net/get_set/article/details/79466657

2、https://www.bilibili.com/video/BV1y44y117pp?share_source=copy_web

springwebflux学习记录(代码片段)

本文按照如下顺序一步步深入解释WebFlux是个什么东西:1、ReactiveStream2、Reactor3、WebFlux其中ReactiveStream是Java9新增的一个重要特性,而Reactor就相当于Java8的Stream流+Java9的ReactiveStream,最后的WebFlux的核心就是基于Reactor... 查看详情

如何在springwebflux中记录请求和响应主体(代码片段)

我希望使用Kotlin在SpringWebFlux上的RESTAPI中集中记录请求和响应。到目前为止,我已经尝试过这种方法@BeanfunapiRouter()=router(accept(MediaType.APPLICATION_JSON)and"/api").nest"/user".nestGET("/",userHandler::listUsers)POST("/userId", 查看详情

springwebflux阻止了另一个请求(代码片段)

我正在学习Springwebflux,面临一个不理解的情况。Spring版本=2.2.4.RELEASEMy电脑有4个核心。我的类。@RestController@RequestMapping("rest/v1/")publicclassRootControllerprivatefinalWebClientwebClient=WebClient.create("http://localhost: 查看详情

springwebflux构建响应式restfulweb服务(代码片段)

本指南将引导您完成创建“Hello,Spring!”使用SpringWebFlux的rest式web服务(SpringBoot2.0的新版本),然后使用WebClient使用该服务(SpringBoot2.0的新版本)。1、你将建立什么您将使用SpringWebflux和该服务的WebClient使用者构建一个RESTfulweb服务... 查看详情

springwebflux-03webflux编程模型(代码片段)

文章目录webfluxAnnotatedControllers-基于SpringMVC注解定义请求的Webflux开发FunctionalEndpoints_基于函数式的Webflux开发HandlerFunctionRouterFunctionwebfluxhttps://docs.spring.io/spring-framework/docs/current/reference/html/we 查看详情

springwebflux-03webflux编程模型(代码片段)

文章目录webfluxAnnotatedControllers-基于SpringMVC注解定义请求的Webflux开发FunctionalEndpoints_基于函数式的Webflux开发HandlerFunctionRouterFunctionwebfluxhttps://docs.spring.io/spring-framework/docs/current/reference/html/we 查看详情

响应式spring的道法术器(springwebflux快速上手+全面介绍)(代码片段)

1.SpringWebFlux2小时快速入门Spring5之使用SpringWebFlux开发响应式应用。lambda与函数式(15min)Reactor3响应式编程库(60min)SpringWebflux和SpringDataReactive开发响应式应用(45min)通过以上内容相信可以对Spring5.0推出的响应式开发有了初步的... 查看详情

springwebflux-01mvc的困境(代码片段)

文章目录SpringMVC的困境Servlet异步请求缓解线程池压力Servlet3.0异步请求处理Code演示工程pom配置文件启动类同步servlet演示异步servlet辅助Code演示Tomcat请求处理流程以及异步请求工作原理SpringMVC的困境我们先看一段工作中大家常见的... 查看详情

springwebflux-01mvc的困境(代码片段)

文章目录SpringMVC的困境Servlet异步请求缓解线程池压力Servlet3.0异步请求处理Code演示工程pom配置文件启动类同步servlet演示异步servlet辅助Code演示Tomcat请求处理流程以及异步请求工作原理SpringMVC的困境我们先看一段工作中大家常见的... 查看详情

springwebflux和webservice同一个工程(代码片段)

基于SpringBoot创建一个SOAPWebService服务的步骤之前发布过在springboot中基于jaxws发布WebService的文章,是基于注解式的(cxf封装过的starter)。本文是基于webflux框架下的代码工程中,用另外一种方式发布webservice。1.初始化一个原始的spr... 查看详情

solidity学习记录——第二章(代码片段)

Solidity学习记录第一章创建生产僵尸的工厂第二章设置僵尸的攻击功能文章目录Solidity学习记录前言一、本章主要目的二、学习过程1.本节课程知识点2.最终代码总结前言本人平时比较忙,只能在周末自学Solidity,尽量在周... 查看详情

python入门基础学习记录汇率案例学习记录(代码片段)

一、汇总整理1.操作①新建python文件工程右键--new--pythonfile 2.注意问题与知识点 》变量定义:直接写变量名即可,例如定义一个字符串并赋值123:rmb_str=‘123’。特别需要注意的,python对格式的要求,等号左右要有... 查看详情

springwebflux后端处理前端请求的4种方式(代码片段)

SpringWebflux是一个无响应系统。它具有某些特性,使其成为低延迟、高吞吐量工作负载的理想选择。ProjectReactor和Spring组合一起工作,使开发人员能够构建响应性、弹性、弹性和消息驱动的企业级反应系统。Spring组合提供... 查看详情

springwebflux后端处理前端请求的4种方式(代码片段)

SpringWebflux是一个无响应系统。它具有某些特性,使其成为低延迟、高吞吐量工作负载的理想选择。ProjectReactor和Spring组合一起工作,使开发人员能够构建响应性、弹性、弹性和消息驱动的企业级反应系统。Spring组合提供... 查看详情

pygame学习记录画面更新(代码片段)

画面更新简要描述:-使用pygame.display.flip()进行画面的更新-设置屏幕的颜色importpygame#---------------------------------分割线--------------------------------------------------------pygame.init()#--------------------------------- 查看详情

solr学习记录:gettingstarted(代码片段)

目录Solr学习记录:Gettingstarted1.SolrTutorial2.AQuickOverviewSolr学习记录:Gettingstarted本教程使用环境:java8或者更高版本、Solr8.1、centos71.SolrTutorial1.1简介本篇将用三个部分具体练习以引领对Solr的快速体验。每个练习将基于前一个练习。第... 查看详情

学习记录:快速幂(代码片段)

目录学习记录快速幂快速幂的递归实现非递归实现学习记录快速幂快速幂的递归实现假设要算(7^9),如果采取普通计算,也就是(7*7*7*7*7*7*7*7*7),共需要8次运算。运用二分的思想,先算(7^4),然后通过(7^4*7^4*7)来计算$7^9$,这样就... 查看详情

solidity学习记录——第五章(代码片段)

Solidity学习记录第一章创建生产僵尸的工厂第二章设置僵尸的攻击功能第三章编写DAPP所需的基础理论第四章完善僵尸功能第五章ERC721标准和加密资产文章目录Solidity学习记录前言一、本章主要目的二、学习过程1.本节课程知识点2.... 查看详情