• Spring-webflux 响应式编程


    热爱可抵漫长岁月

    1. 前言

    Spring 提供了两个并行堆栈。一种是基于带有 Spring MVC 和 Spring Data 结构的 Servlet API。另一个是完全反应式堆栈,它利用了 Spring WebFlux 和 Spring Data 的反应式存储库。在这两种情况下,Spring Security 都提供了对两种堆栈的支持。

    在这里插入图片描述

    反应式宣言

    在这里插入图片描述

    2. Spring-webflux简介

    Spring WebFlux 是在 5.0 版中添加的。它是完全无阻塞的,支持 Reactive Streams背压,并且可以在 Netty、Undertow 和 Servlet 3.1+ 容器等服务器上运行。

    Spring-webflux官网

    3. 什么是“响应式

    所谓响应式,举个例子,当调用一个api获取数据时,无需阻塞等待数据返回,而是当有数据返回时会进行告知。可见响应式是非阻塞的,意味着调用方法后,CPU可以去做别的事情,当接收到数据响应时CPU再回来处理,这种方式提高了系统的吞吐量。

    而响应式编程,其实是为这种异步非阻塞的流式编程制定的一套标准。流式编程已不陌生了,Java8提供的stream api就是这种风格。这套标准包括对运行环境(JVM、JavaScript)以及网络协议相关的规范。

    和传统的阻塞式servlet容器不一样。响应式容器能进一步提高资源的利用率,避免线程长时间处于等待状态,能以较少的线程处理更多的请求,缺点是整个处理链路必须是异步的,是基于事件响应的,不能阻塞事件线程,不然服务器性能会急剧下降,当然spring webflux并不能完整的替代传统的阻塞式容器,可根据需求进行选型。

    应用案例Geteway

    在这里插入图片描述

    所有微服务的请求都会通过网关,如果采用mvc 对于并发量有一定的瓶颈。

    4. Spring-webflux的响应式API

    Spring-webflux框架是基于Reactor这个开源项目开发的。Reactor框架是跟Spring紧密配合的。

    里边提供了两种API类型,分别是MonoFlux

    • Mono表示0 或 1个元素,
    • Flux表示0 至 N个元素,

    5. Spring MVC 还是 WebFlux?

    这两个web框架分别代表着两种不同类型的编程流派,官方给出了一个图作为对比如下

    在这里插入图片描述

    建议考虑以下具体点:

    • 如果您有一个运行良好的 Spring MVC 应用程序,则无需更改。命令式编程是编写、理解和调试代码的最简单方法。您可以选择最多的库,因为从历史上看,大多数都是阻塞的

    • Spring WebFlux 提供与该领域中其他人相同的执行模型优势,并且还提供服务器选择(Netty、Tomcat、Jetty、Undertow 和 Servlet 3.1+ 容器)、编程模型(带注释的控制器和功能性 Web 端点)的选择,以及反应库(Reactor、RxJava 或其他)的选择。

    • 如果您对用于 Java 8 lambda 或 Kotlin 的轻量级、功能性 Web 框架感兴趣,您可以使用 Spring WebFlux 功能性 Web 端点。对于要求不那么复杂的小型应用程序或微服务来说,这也是一个不错的选择,它们可以从更高的透明度和控制中受益。

    • 在微服务架构中,您可以混合使用带有 Spring MVC 或 Spring WebFlux 控制器或带有 Spring WebFlux 功能端点的应用程序。在两个框架中都支持相同的基于注释的编程模型,可以更轻松地重用知识,同时为正确的工作选择正确的工具。

    • 评估应用程序的一种简单方法是检查其依赖关系。如果您要使用阻塞持久性 API(JPA、JDBC)或网络 API,那么 Spring MVC 至少是常见架构的最佳选择。Reactor 和 RxJava 在单独的线程上执行阻塞调用在技术上是可行的,但您不会充分利用非阻塞 Web 堆栈。

    • 如果您有一个调用远程服务的 Spring MVC 应用程序,请尝试响应式WebClient. 您可以直接从 Spring MVC 控制器方法返回反应类型(Reactor、RxJava或其他)。每个呼叫的延迟或呼叫之间的相互依赖性越大,好处就越显着。Spring MVC 控制器也可以调用其他响应式组件。

    • 如果您有一个大型团队,请记住向非阻塞、函数式和声明式编程转变的陡峭学习曲线。在没有完全开关的情况下启动的一种实用方法是使用 reactive WebClient。除此之外,从小处着手并衡量收益。我们预计,对于广泛的应用,这种转变是不必要的。如果您不确定要寻找什么好处,请先了解非阻塞 I/O 的工作原理(例如,单线程 Node.js 上的并发性)及其影响。

    其次: webflux兼容大部分springmvc的注解,也可以像mvc那样创建controller处理请求。

    区别:

    • WebFlux是完全异步非阻塞的,SpringMVC是同步阻塞的。
    • WebFlux采用异步响应式编程,SpringMVC采用命令式编程。
    • WebFlux由于完全异步,所有操作数据库的框架,以及数据库也都要求是支持异步的,所以目前不支持Mybatis、不支持Oracle数据库。

    6. 并发模型

    尽管webmvcwebflux都支持使用注解来定义一个Controller,但是其实现方式完全不同。

    webmvc是一个Servlet应用,实现是阻塞式IO,其维护一个线程池来处理每一个用户请求,也就是当Servlet容器启动时,就会创建比如10个线程出来,因此系统吞吐量的瓶颈在于有限的连接数和阻塞的请求处理过程。

    webflux可以基于netty这样的NIO网络框架,它只需要很少的几个工作线程(Event loop worker)就能够处理并响应请求。由于无需阻塞等待方法返回,CPU资源就得到了更好的利用。

    webflux并不能让程序运行地更快;而是提高了并发处理请求的能力,即提高系统吞吐量

    7. webflux使用

    pom依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webfluxartifactId>
            <version>2.5.9version>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义对象:

    public class Person {
        private Integer id;
        private Integer age;
        private String name;
    
        public Person(Integer id, Integer age, String name) {
            this.id = id;
            this.age = age;
            this.name = name;
        }
    
        public Person() {
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "id=" + id +
                    ", age=" + age +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    然后定义PersonController,响应式风格中不再使用@RequestMapping声明地址映射,而是通过RouterFunctions.route().GET()方法:

    @Configuration
    public class PersonRouter {
        @Resource
        private PersonHandler personHandler;
        @Bean
        public RouterFunction<ServerResponse> personRoutes() {
            return RouterFunctions.route()
                    .GET("/person/{id}", RequestPredicates.accept(MediaType.APPLICATION_JSON), personHandler::getPerson)
                    .GET("/person", RequestPredicates.accept(MediaType.APPLICATION_JSON), personHandler::listPeople)
                    .POST("/person", personHandler::createPerson)
                    .build();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    PersonHandler中处理对应的HTTP请求,等同于MVC架构中的Service层

    @Component
    public class PersonHandler {
    
        @Resource
        private IPersonDao personDao;
    
        public Mono<ServerResponse> listPeople(ServerRequest request) {
            Flux<Person> people = personDao.getList();
            return ServerResponse.ok()
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(people, Person.class);
        }
    
        public Mono<ServerResponse> createPerson(ServerRequest request) {
            return request.bodyToMono(Person.class)
                    .flatMap(i -> personDao.savePerson(i))
                    .flatMap(p -> ServerResponse.ok().bodyValue(p));
        }
    
        public Mono<ServerResponse> getPerson(ServerRequest request) {
            int personId = Integer.parseInt(request.pathVariable("id"));
            return personDao.getPerson(personId)
                    .flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue(person))
                    .switchIfEmpty(ServerResponse.notFound().build());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    IPersonDao

    public interface IPersonDao {
    
        /**
         * 获取所有person
         * @author: yh
         * @date: 2022/9/4
         * @return Flux
         */
        Flux<Person> getList();
    
        /**
         * 保存对象
         * @param person person
         * @author: yh
         * @date: 2022/9/4
         * @return Mono
         */
        Mono<Void> savePerson(Person person);
    
        /**
         * 根据id查询
         * @param id id
         * @author: yh
         * @date: 2022/9/4
         * @return Mono
         */
        Mono<Person> getPerson(Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    PersonDao

    @Component
    public class PersonDao implements IPersonDao {
    
        private final List<Person> personList = new ArrayList<>();
    
        public PersonDao() {
            this.personList.add(new Person(1, 17, "张三"));
            this.personList.add(new Person(2, 18, "李四"));
        }
    
        @Override
        public Flux<Person> getList() {
            return Flux.fromIterable(personList);
        }
    
        @Override
        public Mono<Void> savePerson(Person person) {
            personList.add(person);
            System.out.println("personList.size = " + personList);
            return Mono.empty();
        }
    
        @Override
        public Mono<Person> getPerson(Integer id) {
            return Mono.justOrEmpty(personList.stream().filter(p -> p.getId().equals(id)).findFirst());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    @SpringBootApplication
    @EnableWebFlux
    public class SpringWebfluxSessionApplication implements WebFluxConfigurer {
        public static void main(String[] args) {
            SpringApplication.run(SpringWebfluxSessionApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8. 测试

    通过启动日志可以证实Spring-webflux是默认使用Netty提供HTTP服务

    在这里插入图片描述

    GET请求:http://127.0.0.1:8080/person
    在这里插入图片描述

    POST请求:http://127.0.0.1:8080/person

    boyd:

    {
        "id": 9,
        "age": 17,
        "name": "张三"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    控制台输出:
    在这里插入图片描述

    GET请求:http://127.0.0.1:8080/person/9
    在这里插入图片描述


    完整代码已上传 Gitee Spring整合常用组件

    到此,本章内容就介绍完啦,如果有帮助到你 欢迎点个赞👍👍👍吧!!您的鼓励是博主的最大动力! 有问题评论区交流。

  • 相关阅读:
    前端的ajax你知多少?ajax完全指南奉上
    kaf操作命令
    python加密Django框架代码(通过修改Cpython解释器)
    谷粒商城-2
    王杰C++day5
    第十届全球云计算大会 | 华云数据荣获“2013-2022十周年特别贡献奖”
    进程和线程
    Java for循环每次都通过list.size()和 string.length()获取大小性能
    (附源码)springboot计算机专业大学生就业指南网 毕业设计 061355
    维度建模之汇总分析表的设计经验分享
  • 原文地址:https://blog.csdn.net/weixin_43847283/article/details/126692669