27、SpringBoot实战:集成WebFlux

目录

    • 一、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等注解,类似SpringMVC的开发体验;
2、 **函数式编程控制器:**使用Java8函数式接口定义路由和处理逻辑;

1.2 WebFlux 与 Spring MVC 区别

WebFlux:

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

Spring MVC:

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

版权声明:本文不是「本站」原创文章,版权归原作者所有 | 原文地址: