SpringBoot实战(二十七)集成WebFlux

本文详细介绍了SpringFramework5中的WebFlux模块,对比了其与SpringMVC的异步、非阻塞特性,以及通过注解式控制器和函数式编程实现RESTfulAPI的方法。通过实例展示了如何在Maven中配置和测试WebFlux应用。
摘要由CSDN通过智能技术生成

一、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

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值