目录
- 一、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 提供了两种编程模型:
- 注解式控制器: 使用
@Controller
等注解,类似 Spring MVC 的开发体验。 - 函数式编程控制器: 使用 Java 8 函数式接口定义路由和处理逻辑。
1.2 WebFlux 与 Spring MVC 区别
WebFlux:
- 异步非阻塞: WebFlux 基于反应式编程模型,支持非阻塞 I/O,能够充分利用多核 CPU 资源,并且在高并发场景下具有更好的性能表现,因为 它不会为每个请求分配独立的线程,从而避免了线程上下文切换带来的开销。
- 响应式编程: WebFlux 使用
Project Reactor
(或者 RxJava 作为备选)提供的 Mono 和 Flux 类型来表示可能零个、一个或多个事件的异步序列,使得开发者可以编写异步数据处理逻辑。 - 无需 Servlet API: 尽管可以在 Servlet 容器上运行,但它不直接依赖 Servlet API,能在非阻塞服务器(如
Netty
、Undertow
等)上运行。 - 函数式编程风格: 除了提供类似于 Spring MVC 的注解式编程模型外,WebFlux 还支持函数式编程模型,允许通过
RouterFunction
等方式进行更灵活的路由配置。
Spring MVC:
- 同步阻塞: Spring MVC 基于传统的 Servlet API,每个 HTTP 请求通常都会绑定到一个单独的线程直到请求处理完成并发送响应为止。
- 线程模型: 在默认情况下,Spring MVC 应用中,每个请求会创建或从线程池获取一个线程,处理完成后释放回线程池。这种模式在请求处理复杂度较高或线程池大小受限时,可能会影响系统的并发能力。
- 依赖 Servlet 容器: Spring MVC 必须部署在支持 Servlet API 的容器中运行(如:Tomcat、Jetty、Undertow、Weblogic等)。
- 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