SpringBoot实战(二十七)集成WebFlux

news2024/9/23 11:13:32

目录

    • 一、WebFlux
      • 1.1 定义
      • 1.2 WebFlux 与 Spring MVC 区别
    • 二、代码实现
      • 2.1 Maven 配置
      • 2.2 暴露 RESTful API 接口的方式
        • 方式一:基于注解的控制器
        • 方式二:函数式路由器(Functional Endpoints)
      • 2.3 测试Service
      • 2.4 测试ServiceImpl
      • 2.5 测试实体类
      • 2.6 启动类
    • 三、测试结果
      • 3.1 基于注解的控制器-测试
      • 3.2 函数式路由器-测试
        • 1)添加用户接口
        • 2)查询所有用户接口
        • 3)根据ID查询用户接口

一、WebFlux

1.1 定义

WebFlux 是 Spring Framework 5 引入的一个模块,它是一个 非阻塞的、异步的、响应式的 Web 开发框架。WebFlux 设计的核心是为了 使用现代 Web 应用对于高并发、低延迟和高吞吐量的需求,它采用 Reactive 编程模型,通过 Reactor 库实现了异步数据流处理。

  • 在 WebFlux 中,HTTP 请求和响应被建模为 Mono(代表 0~1 个元素的异步序列)和 Flux(代表 0~N个元素的异步序列)类型,这些都是 Reactive Streams 规范的一部分。这意味着 开发者可以通过声明式和函数式编程风格来处理请求和响应的数据流。

WebFlux 提供了两种编程模型:

  1. 注解式控制器: 使用 @Controller 等注解,类似 Spring MVC 的开发体验。
  2. 函数式编程控制器: 使用 Java 8 函数式接口定义路由和处理逻辑。

1.2 WebFlux 与 Spring MVC 区别

WebFlux:

  1. 异步非阻塞: WebFlux 基于反应式编程模型,支持非阻塞 I/O,能够充分利用多核 CPU 资源,并且在高并发场景下具有更好的性能表现,因为 它不会为每个请求分配独立的线程,从而避免了线程上下文切换带来的开销
  2. 响应式编程: WebFlux 使用 Project Reactor(或者 RxJava 作为备选)提供的 Mono 和 Flux 类型来表示可能零个、一个或多个事件的异步序列,使得开发者可以编写异步数据处理逻辑。
  3. 无需 Servlet API: 尽管可以在 Servlet 容器上运行,但它不直接依赖 Servlet API,能在非阻塞服务器(如 NettyUndertow 等)上运行。
  4. 函数式编程风格: 除了提供类似于 Spring MVC 的注解式编程模型外,WebFlux 还支持函数式编程模型,允许通过 RouterFunction 等方式进行更灵活的路由配置。

Spring MVC:

  1. 同步阻塞: Spring MVC 基于传统的 Servlet API,每个 HTTP 请求通常都会绑定到一个单独的线程直到请求处理完成并发送响应为止
  2. 线程模型: 在默认情况下,Spring MVC 应用中,每个请求会创建或从线程池获取一个线程,处理完成后释放回线程池。这种模式在请求处理复杂度较高或线程池大小受限时,可能会影响系统的并发能力。
  3. 依赖 Servlet 容器: Spring MVC 必须部署在支持 Servlet API 的容器中运行(如:Tomcat、Jetty、Undertow、Weblogic等)。
  4. API 和编程模型: Spring MVC 主要采用注解驱动的方式组织控制器和处理请求响应,例如通过 @Controller@RequestMapping 等注解。

二、代码实现

2.1 Maven 配置

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

2.2 暴露 RESTful API 接口的方式

在 Spring WebFlux 框架中,暴露 RESTful API 接口主要有以下两种方式:

方式一:基于注解的控制器
  • 使用 @RestController 注解定义一个控制器类,通过 @RequestMapping@GetMapping@PostMapping 等注解来指定请求路径和 HTTP 方法,处理客户端的请求和响应。

DemoController.java

import com.demo.service.DemoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;

/**
 * <p> @Title DemoController
 * <p> @Description 测试Controller
 *
 * @author ACGkaka
 * @date 2023/4/24 18:02
 */
@Slf4j
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Resource
    private DemoService demoService;

    /**
     * webflux接口测试(返回 0个 或 1个结果)
     */
    @GetMapping("/monoTest")
    public Mono<Object> monoTest() {
        /// 写法一:命令式写法
//        String data = getOneResult("monoTest()");
//        return Mono.just(data);

        // 写法二:响应式写法(语句需要在流中执行)
        return Mono.create(cityMonoSink -> {
            String data = demoService.getOneResult("monoTest()");
            cityMonoSink.success(data);
        });
    }

    /**
     * webflux接口测试(返回 0个 或 多个结果)
     */
    @GetMapping("/fluxTest")
    public Flux<Object> fluxTest() {
        // 写法一:命令式写法
//        List<String> list = getMultiResult("fluxTest()");
//        return Flux.fromIterable(list);

        // 写法二:响应式写法(语句需要在流中执行)
        return Flux.fromIterable(demoService.getMultiResult("fluxTest()"));
    }

}
方式二:函数式路由器(Functional Endpoints)
  • 使用 RouterFunctions.route() 或者 RouterFunction<ServerResponse> 来创建路由函数,这种方式更加函数式和声明式。

RouteConfig.java

import com.demo.config.handler.UserReactiveHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

import javax.annotation.Resource;

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;

/**
 * <p> @Title RouteConfig
 * <p> @Description 路由配置
 *
 * @author ACGkaka
 * @date 2024/3/21 13:39
 */
@Configuration
public class RouteConfig {
    @Resource
    private UserReactiveHandler handler;

    @Bean
    public RouterFunction<ServerResponse> routes() {
        // 下面的操作相当于 @RequestMapping
        return RouterFunctions.route(POST("/addUser"), handler::addUser)
                .andRoute(GET("/userList"), handler::userList)
                .andRoute(GET("/findUserById/{id}"), handler::findUserById);
    }
}

2.3 测试Service

DemoService.java

import java.util.List;

/**
 * <p> @Title DemoService
 * <p> @Description 测试Service
 *
 * @author ACGkaka
 * @date 2024/3/20 11:46
 */
public interface DemoService {

    /**
     * 模拟业务处理,返回单个结果
     */
    String getOneResult(String methodName);

    /**
     * 模拟业务处理,返回多个结果
     */
    List<String> getMultiResult(String methodName);

    /**
     * 添加用户
     */
    User addUser(User user);

    /**
     * 查询所有用户
     */
    List<User> findAllUser();

    /**
     * 根据 id 查询用户
     */
    User findUserById(Long id);

}

2.4 测试ServiceImpl

DemoServiceImpl.java

import com.demo.entity.User;
import com.demo.service.DemoService;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * <p> @Title DemoServiceImpl
 * <p> @Description 测试ServiceImpl
 *
 * @author ACGkaka
 * @date 2024/3/20 11:46
 */
@Service
public class DemoServiceImpl implements DemoService {

    @Override
    public String getOneResult(String methodName) {
        // 模拟业务处理,返回单个结果
        return String.format("%s方法调用成功", methodName);
    }

    @Override
    public List<String> getMultiResult(String methodName) {
        // 模拟业务处理,返回多个结果
        List<String> list = new ArrayList<>(3);
        for (int i = 0; i < 3; i++) {
            list.add(String.format("%s方法调用成功,第 %d 条", methodName, i + 1));
        }
        return list;
    }

    @Override
    public User addUser(User user) {
        // 添加用户
        user.setId(1L);
        return user;
    }

    @Override
    public List<User> findAllUser() {
        // 查询所有用户
        List<User> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            int no = i + 1;
            list.add(new User((long) no, "USER_" + no, "PWD_" + no, 18 + no));
        }
        return list;
    }

    @Override
    public User findUserById(Long id) {
        // 根据 id 查询用户
        return new User(id, "USER_" + id, "PWD_" + id, 18);
    }
}

2.5 测试实体类

User.java

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * <p> @Title User
 * <p> @Description 用户信息
 *
 * @author ACGkaka
 * @date 2024/3/21 11:12
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    /**
     * 主键
     */
    private Long id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 年龄
     */
    private Integer age;
}

2.6 启动类

SpringbootDemoApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class SpringbootDemoApplication {

    /**
     * 可以使用以下两种方式创建 ApplicationContext
     */
    public static void main(String[] args) {
        // 方式一
        SpringApplication.run(SpringbootDemoApplication.class, args);

        // 方式二:使用 SpringApplicationBuilder 来创建 SpringApplication。
        // builder 提供了链式调用 API,更加方便,可读性更强。
//        SpringApplicationBuilder builder = new SpringApplicationBuilder()
//                .web(WebApplicationType.REACTIVE).sources(SpringbootDemoApplication.class);
//        builder.run(args);
    }

}

三、测试结果

3.1 基于注解的控制器-测试

Mono<T> 返回类型的接口测试:

  • 请求地址: http://localhost:8080/demo/monoTest

  • 请求结果:

在这里插入图片描述

Flux<T> 返回类型的接口测试:

  • 请求地址: http://localhost:8080/demo/fluxTest
  • 请求结果:

在这里插入图片描述

3.2 函数式路由器-测试

1)添加用户接口
  • 请求地址: http://localhost:8080/addUser
  • 请求结果:(失败测试)

在这里插入图片描述

  • 请求结果:(成功测试)

在这里插入图片描述

2)查询所有用户接口
  • 请求地址: http://localhost:8080/userList
  • 请求结果:

在这里插入图片描述

3)根据ID查询用户接口
  • 请求地址: http://localhost:8080/findUserById/123
  • 请求结果:

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.webflux + springboot 整合(史上最全),https://blog.csdn.net/crazymakercircle/article/details/112977951

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

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

相关文章

c语言(动态内存管理函数)

1. 为什么要有动态内存分配 我们已经掌握的内存开辟⽅式有&#xff1a; int arr[10] {0}; char a; 但是上述的开辟空间的⽅式有两个特点&#xff1a; 但是上述的开辟空间的⽅式有两个特点&#xff1a; • 空间开辟⼤⼩是固定的。 • 数组在申明的时候&#xff0c;必须指…

vmare17 安装不可启动的iso镜像系统

由于要测试一个软件&#xff0c;要安装一个Windows11_InsiderPreview_Client_x64_zh-cn_26058.iso 于是在虚拟机里捣鼓一下。但是这个iso好像不能直接启动 这样就无法直接安装了&#xff0c;怎么办呢&#xff0c;可以先用个pe系统引导进去&#xff0c;再在PE系统里安装这个iso…

可观测性平台如何助推保险行业数智化转型与升级

近日&#xff0c;主题为“人工智能大模型应用与保险业信创建设”的华东地区保险业IT微沙龙在江西圆满落幕。活动汇聚了众多保险行业的信息技术领军人物&#xff0c;他们为行业的科技创新与转型发展提供了重要的思路与方向。博睿数据作为中国IT运维监控和可观测性领域领导者受邀…

AES,DES

AES加密过程 初始轮&#xff08;Initial Round&#xff09;&#xff1a; 将明文分组与初始轮密钥&#xff08;Round Key&#xff09;进行XOR运算。轮运算&#xff08;Rounds&#xff09;&#xff1a;AES算法中的加密运算是由多轮执行的&#xff0c;每一轮都包含四个基本步骤&…

LLaVA: Large Language and Vision Assistant 图片解析

LLaVA: Large Language and Vision Assistant 图片解析 目录 介绍 效果 ​编辑项目 测试代码 Form1.cs Helper.cs 下载 介绍 LLaVA&#xff0c;一种新的大型多模态模型&#xff0c;称为“大型语言和视觉助手”&#xff0c;旨在开发一种通用视觉助手&#xff0c;可以遵…

智慧矿山新趋势:大数据解决方案一览

1. 背景 随着信息技术的快速发展和矿山管理需求的日益迫切&#xff0c;智慧矿山作为一种创新的矿山管理方式应运而生。智慧矿山借助先进的信息技术&#xff0c;实现对矿山生产、管理、安全等各方面的智能化、高效化、协同化&#xff0c;是矿山行业转型升级的必然趋势。 欢迎关…

电子版合同的法律地位-复制品还是替代品?

电子合同与电子版合同并不完全等同&#xff0c;它们之间存在一些关键的区别。以下是对两者的专业解读&#xff1a; 电子合同 定义&#xff1a;电子合同是指完全以电子形式存在的合同&#xff0c;双方或多方通过电子设备进行协商、签署和履行。它不依赖于纸质文件&#xff0c;…

Java基于微信小程序的二手交易系统的实现(V2.0)

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、Python 技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#…

#Linux(文件系统概念)

&#xff08;一&#xff09;发行版&#xff1a;Ubuntu16.04.7 &#xff08;二&#xff09;记录&#xff1a; &#xff08;1&#xff09;查看文件系统情况df&#xff0c;man df查看df命令的功能 &#xff08;2&#xff09;查看文件系统的类型 df-T &#xff08;3&#xff09;df …

前端开发经验分享:写页面时总是有预期之外的滚动条怎么办?

问题描述&#xff1a; 在制作一个页面时常常会出现一些预期之外的滚动条&#xff0c;一般有以下原因&#xff1a;1.内容过多&#xff1a;当容器内的内容&#xff08;如文本、图片等&#xff09;的总高度或总宽度超过容器的可视区域时&#xff0c;滚动条就会出现。2.样式设置&a…

Android Handler使用介绍

Android 中的 Handler 是用来和线程通信的重要工具。它主要用于在后台线程中执行任务&#xff0c;并将结果传递回主线程以更新用户界面。 一、基本概念 线程间通信&#xff1a; Android 应用通常具有主线程&#xff08;也称为 UI 线程&#xff09;和后台线程。Handler 允许您从…

有些商标名称慎加通用词,可能会以误认驳回!

近期看到一网友在30类方便食品申请"某某茶叶"&#xff0c;这个商标名称部分商品通过一部分&#xff0c;另一部分商品以误认驳回&#xff0c;普推知产老杨分析时发现在以前很少出现这种情况&#xff0c;但是近年来商标名称加通用词误认驳回的比较多。 现阶段这种带通…

深度解析ThreadLocal:底层原理、数据隔离与内存泄漏解决

前言 这个问题算是我的一个羞耻点&#xff0c;起源于一次面试中&#xff0c;面试官问ThreadLocal的底层实现是啥&#xff0c;我那时候一直以为ThreadLocal是一个类似于Redis一样的独立于线程外的第三方存储容器&#xff0c;如何底层维护了一个Map结构&#xff0c;以线程ID为Key…

专题一——双指针算法

原理&#xff1a;将数组进行区间划分&#xff0c;通过指针(下标)的移动实现题目所要求的区间&#xff08;数组分块&#xff09; &#xff08;实现代码统一是C&#xff09; 建议在做题与看题解时要自己反复模拟这个实现的过程&#xff0c;以后在做题做到类似的题才能举一反三&am…

QT6实现创建与操作sqlite数据库及读取实例(一)

一.Qt为SQL数据库提供支持的基本模块&#xff08;Qt SQL&#xff09; Qt SQL的API分为不同层&#xff1a; 驱动层 SQL API层 用户接口层 1.驱动层 对于Qt 是基于C来实现的框架&#xff0c;该层主要包括QSqlDriver&#xff0c;QSqlDriverCreator,QSqlDriverCreatorBase,QSqlPlug…

Linux第78步_使用原子整型操作来实现“互斥访问”共享资源

使用原子操作来实现“互斥访问”LED灯设备&#xff0c;目的是每次只允许一个应用程序使用LED灯。 1、创建MyAtomicLED目录 输入“cd /home/zgq/linux/Linux_Drivers/回车” 切换到“/home/zgq/linux/Linux_Drivers/”目录 输入“mkdir MyAtomicLED回车”&#xff0c;创建MyA…

Python从 Google 地图空气质量 API 获取空气污染数据

获取给定位置当前的空气质量 让我们开始吧!在本节中,我们将介绍如何使用 Google 地图获取给定位置的空气质量数据。您首先需要一个 API 密钥,可以通过您的 Google Cloud 帐户生成该密钥。他们有90 天的免费试用期,之后您将为您使用的 API 服务付费。在开始大量拨打电话之前…

51单片机中断信号的种类及应用场景

在嵌入式系统中&#xff0c;中断是一种重要的事件处理机制&#xff0c;它可以在程序执行的任何时候暂停当前任务&#xff0c;转而执行与之相关的特殊任务或事件。51单片机作为一种常见的微控制器&#xff0c;其中断功能在各种应用中起着关键作用。然而&#xff0c;对于初学者和…

一、SpringBoot基础搭建

本教程主要给初学SpringBoot的开发者&#xff0c;通过idea搭建单体服务提供手把手教学例程&#xff0c;主要目的在于理解环境的搭建&#xff0c;以及maven模块之间的整合与调用 源码&#xff1a;jun/learn-springboot 以商城项目为搭建例子&#xff0c;首先计划建1个父模块&…

部署单节点k8s并允许master节点调度pod

安装k8s 需要注意的是k8s1.24 已经弃用dockershim&#xff0c;现在使用docker需要cri-docker插件作为垫片&#xff0c;对接k8s的CRI。 硬件环境&#xff1a; 2c2g 主机环境&#xff1a; CentOS Linux release 7.9.2009 (Core) IP地址&#xff1a; 192.168.44.161 一、 主机配…