Spring WebFlux
一、简介
Spring WebFlux
作为一个响应式(reactive-stack
)web
框架补充,在5.0
的版本开始加入到Spring
全家桶。这是一个完全非阻塞的,支持Reactive Streams
,运行在诸如Netty
,Undertow
, 以及Servlet 3.1+
容器上的。Spring WebFlux
可以让你使用更少的线程去处理并发请求,同时能够让你使用更少的硬件资源来拓展你的应用。
1.1 Spring WebFlux与Spring MVC的区别
结合上图,在了解Spring WebFlux
之前,我们先来对比说说什么是Spring MVC
,这更有益我们去理解WebFlux
,图右边对Spring MVC
的定义,原文如下:
Spring MVC is built on the Servlet API and uses a synchronous blocking I/O architecture with a one-request-per-thread model.
翻译一下,意思如下:
Spring MVC构建于Servlet API之上,使用的是同步阻塞式I/O模型,什么是同步阻塞式I/O模型呢?就是说,每一个请求对应一个线程去处理。
了解了Spring MVC
之后,再来说说Spring WebFlux
:
上图左边,官方给出的定义如下:
Spring WebFlux is a non-blocking web framework built from the ground up to take advantage of multi-core, next-generation processors and handle massive numbers of concurrent connections.
翻译一下,内容如下:
Spring WebFlux是一个异步非阻塞式的Web框架,它能够充分利用多核CPU的硬件资源去处理大量的并发请求。
1.2 WebFlux的优势&提升性能?
WebFlux
内部使用的是响应式编程(Reactive Programming
),以Reactor
库为基础,基于异步和事件驱动,可以让我们在不扩充硬件资源的前提下,提升系统的吞吐量和伸缩性。
看到这里,你是不是以为WebFlux
能够使程序运行的更快呢?量化一点,比如说我使用WebFlux
以后,一个接口的请求响应时间是不是就缩短了呢?
抱歉了,答案是否定的!,以下是官方原话:
Reactive and non-blocking generally do not make applications run faster.
WebFlux并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性 。
1.3 WebFlux应用场景
上面说到了,Spring WebFlux
是一个异步非阻塞式的Web
框架,所以,它特别适合应用在IO
密集型的服务中,比如微服务网关这样的应用中。
PS:IO密集型包括:磁盘IO密集型,网络IO密集型,微服务网关就属于网络IO密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。
1.4 选WebFlux还是Spring MVC?
首先你需要明确一点就是:WebFlux不是Spring MVC的替代方案,虽然WebFlux
也可以被运行在Servlet
容器上(需是Servlet 3.1+
以上的容器),但是WebFlux
主要还是应用在异步非阻塞编程模型,而Spring MVC
是同步阻塞的,如果你目前在Spring MVC
框架中大量使用非同步方案,那么,WebFlux
才是你想要的,否则,使用Spring MVC
才是你的首选。
在微服务架构中,Spring MVC
和WebFlux
可以混合使用,比如已经提到的,对于那些IO
密集型服务(如网关),我们就可以使用WebFlux
来实现。
总之一句话,在合适的场景中,选型最合适的技术。
1.5 异同点
从上图中,可以一眼看出Spring MVC
和Spring WebFlux
的相同点和不同点:
相同点:
- 都可以使用
Spring MVC
注解,如@Controller
,方便我们在两个Web
框架中自由转换; - 均可以使用
Tomcat
,Jetty
,Undertow Servlet
容器(Servlet 3.1+
); - ...
注意点:
Spring MVC
因为是使用的同步阻塞式,更方便开发人员编写功能代码,Debug
测试等,一般来说,如果Spring MVC
能够满足的场景,就尽量不要用WebFlux
;WebFlux
默认情况下使用Netty
作为服务器;WebFlux
不支持MySql
;
1.6 WebFlux是如何分发请求的
使用过Spring MVC
的,应该到知道Spring MVC
的前端控制器是DispatcherServlet
,而WebFlux
是DispatcherHandler
,它实现了WebHandler
接口:
来看看DispatcherHandler
类中处理请求的handle
方法:
- ①:
ServerWebExchange
对象中放置每一次HTTP
请求响应信息,包括参数等; - ②: 判断整个接口映射
mappings
集合是否为空,空则创建一个Not Found
的错误; - ③: 根据具体的请求地址获取对应的
handlerMapping
; - ④: 调用具体业务方法,也就是我们定义的接口方法;
- ⑤: 处理返回的结果;
二、Webflux请求处理流程
2.1 spring mvc处理流程
在了解SpringMvc
的请求流程源码之后,理解WebFlux
就容易的多,毕竟WebFlux
处理流程是模仿Servlet
另起炉灶的。
下面是spring mvc
的请求处理流程
具体步骤:
- 第一步:发起请求到前端控制器(
DispatcherServlet
) - 第二步:前端控制器请求
HandlerMapping
查找Handler
(可以根据xml
配置、注解进行查找) 匹配条件包括:请求路径、请求方法、header
信息等 - 第三步:处理器映射器
HandlerMapping
向前端控制器返回Handler
,HandlerMapping
会把请求映射为HandlerExecutionChain
对象(包含一个Handler
处理器(页面控制器)对象,多个HandlerInterceptor
拦截器对象),通过这种策略模式,很容易添加新的映射策略HandlerInterceptor
是请求路径上的拦截器,需要自己实现这个接口以拦截请求,做一些对handler
的前置和后置处理工作。 - 第四步:前端控制器调用处理器适配器去执行
Handler
- 第五步:处理器适配器
HandlerAdapter
将会根据适配的结果去执行Handler
- 第六步:
Handler
执行完成给适配器返回ModelAndView
- 第七步:处理器适配器向前端控制器返回
ModelAndView
(ModelAndView
是springmvc
框架的一个底层对象,包括Model
和view
) - 第八步:前端控制器请求视图解析器去进行视图解析(根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
- 第九步:视图解析器向前端控制器返回
View
- 第十步:前端控制器进行视图渲染(视图渲染将模型数据(在
ModelAndView
对象中)填充到request
域) - 第十一步:前端控制器向用户响应结果
2.2 webflux请求处理流程
注解驱动请求的webflux
请求处理流程
我们可以对比SpringMVC
的请求流程图对比来看
我们可以看到,处理流程基本一样,有以下主要的点不同
- 处理核心
- WebFlux--
DispatcherHandler
- SpringMvc--
DispatcherServlet
- WebFlux--
- 返回值处理器
- WebFlux--
HandlerResultHandler
- SpringMvc--
HandlerMethodReturnValueHandler
- WebFlux--
- 内容协商配置器
- WebFlux--
RequestedContentTypeResolverBuilder
- SpringMvc--
ContentNegotiationConfigurer
- WebFlux--
还有很多就不一一例举了,想知道核心组件对比结果的同学,可以看下图。注意很多图上的组件名称相同,但是包的位置是不同的,所以大家要注意区分,不要弄混了。
2.2.1 Web MVC VS. WebFlux 核心组件对比
2.3 核心控制器DispatcherHandler
核心控制器DispatcherHandler
等同于阻塞方式的DispatcherServlet
,DispatcherHandler
实现ApplicationContextAware
,那么必然会调用setApplicationContext
方法
public class DispatcherHandler implements WebHandler, ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
initStrategies(applicationContext);
}
}
2.3.1 initStrategies初始化
获取HandlerMapping,HandlerAdapter,HandlerResultHandler的所有实例
protected void initStrategies(ApplicationContext context) {
//获取HandlerMapping及其子类型的bean
//HandlerMapping根据请求request获取handler执行链
Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false);
ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
//排序
AnnotationAwareOrderComparator.sort(mappings);
this.handlerMappings = Collections.unmodifiableList(mappings);
//获取HandlerAdapter及其子类型的bean
Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false);
this.handlerAdapters = new ArrayList<>(adapterBeans.values());
//排序
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
//获取HandlerResultHandler及其子类型的bean
Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerResultHandler.class, true, false);
this.resultHandlers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(this.resultHandlers);
}
2.3.1 DispatcherHandler的总体流程:
- 1、通过
HandlerMapping
(和DispatcherServlet
中的HandlerMapping
不同)获取到HandlerAdapter
放到ServerWebExchange
的属性中 - 2、获取到
HandlerAdapter
后触发handle
方法,得到HandlerResult
- 3、通过
HandlerResult
,触发handleResult
,针对不同的返回类找到不同的HandlerResultHandler
如视图渲染ViewResolutionResultHandler
、ServerResponseResultHandler
、ResponseBodyResultHandler
、ResponseEntityResultHandler
不同容器有不同的实现,如Reactor
,Jetty
,Tomcat
等。
2.4 HandlerMapping
webflux
中引入了一个新的HandlerMapping
,即RouterFunctionMapping
RouterFunctionMapping
实现了InitializingBean
,因此在其实例化的时候,会调用afterPropertiesSet
方法
public class RouterFunctionMapping extends AbstractHandlerMapping implements InitializingBean {
@Nullable
private RouterFunction<?> routerFunction;
//省略部分代码
//afterPropertiesSet()方法 是组件初始化后回调 必须实现InitializingBean接口
//
@Override
public void afterPropertiesSet() throws Exception {
if (CollectionUtils.isEmpty(this.messageReaders)) {
ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
this.messageReaders = codecConfigurer.getReaders();
}
//初始化routerFunction
if (this.routerFunction == null) {
initRouterFunctions();
}
}
/**
* Initialized the router functions by detecting them in the application context.
* 从应用上下文中查找他们并初始化路由方法
*/
protected void initRouterFunctions() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for router functions in application context: " +
getApplicationContext());
}
//查找合并所有路由方法的bean
List<RouterFunction<?>> routerFunctions = routerFunctions();
if (!CollectionUtils.isEmpty(routerFunctions) && logger.isInfoEnabled()) {
routerFunctions.forEach(routerFunction -> logger.info("Mapped " + routerFunction));
}
//将一个请求中含有多个路由请求方法合并成一个方法
this.routerFunction = routerFunctions.stream()
.reduce(RouterFunction::andOther)
.orElse(null);
}
//查找并合并所有路由方法
private List<RouterFunction<?>> routerFunctions() {
//声明 SortedRouterFunctionsContainer bean
SortedRouterFunctionsContainer container = new SortedRouterFunctionsContainer();
//自动注入到上下文中
obtainApplicationContext().getAutowireCapableBeanFactory().autowireBean(container);
//返回路由
return CollectionUtils.isEmpty(container.routerFunctions)
? Collections.emptyList()
: container.routerFunctions;
}
//省略部分代码
private static class SortedRouterFunctionsContainer {
@Nullable
private List<RouterFunction<?>> routerFunctions;
//由上面的方法 自动注入bean时实现依赖查找,查找所有的 RouterFunction beans
//并注入到 List<RouterFunction<?>> 中。这样就会得到所有实现路由方法的集合
@Autowired(required = false)
public void setRouterFunctions(List<RouterFunction<?>> routerFunctions) {
this.routerFunctions = routerFunctions;
}
}
}
2.5 HandlerAdapter
webflux
中引入了一个新的HandlerAdapter
,即HandlerFunctionAdapter
webflux
中引入了一个新的HandlerResultHandler
,即ServerResponseResultHandler
ServerResponseResultHandler
实现了InitializingBean
,因此在其实例化的时候,会调用afterPropertiesSet
方法
流式处理请求handler()
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
//handlerMappings在initStrategies()方法中已经构造好了
if (this.handlerMappings == null) {
return createNotFoundError();
}
//构造Flux,数据源为handlerMappings集合
return Flux.fromIterable(this.handlerMappings)
//获取Mono<Handler>对象,通过concatMap保证顺序和handlerMappings顺序一致
//严格保证顺序是因为在一个系统中可能存在一个Url有多个能够处理的HandlerMapping的情况
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
//如果next()娶不到值则抛出错误
.switchIfEmpty(createNotFoundError())
//触发HandlerApter的handle方法
.flatMap(handler -> invokeHandler(exchange, handler))
//触发HandlerResultHandler 的handleResult方法
.flatMap(result -> handleResult(exchange, result));
}
触发HandlerApter
的handle
方法
private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
return getResultHandler(result).handleResult(exchange, result)
.onErrorResume(ex -> result.applyExceptionHandler(ex).flatMap(exceptionResult ->
getResultHandler(exceptionResult).handleResult(exchange, exceptionResult)));
}
private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
if (this.resultHandlers != null) {
for (HandlerResultHandler resultHandler : this.resultHandlers) {
if (resultHandler.supports(handlerResult)) {
return resultHandler;
}
}
}
throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
}
2.6 函数式端点请求处理流程
通过上图,我们可以看到,这个处理跟之前的注解驱动请求大有不同,但是请求的流程是万变不离其宗,只是组件有所变化。
接下来我们就跟着流程图一步一步的来解读WebFlux
函数端点式请求的源码。
2.6.1 装配阶段
由上图我们可以看到RouterFunctionMapping
是由WebFluxConfigurationSupport
创建的,接下来看一下RouterFunctions
是怎么合并RouterFunction
的并且如何关联到RouterFunctionMapping
的。
RouterFunctionMapping
的源码,前面已经介绍了。
2.6.2 请求阶段
请求阶段的核心代码就是org.springframework.web.reactive.DispatcherHandler#handle
方法,我们来看一下源码。
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
ServerHttpRequest request = exchange.getRequest();
logger.debug("Processing " + request.getMethodValue()
+ " request for [" + request.getURI() + "]");
}
if (this.handlerMappings == null) {
return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
}
// 1.HTTP请求进来后执行的流程
return Flux.fromIterable(this.handlerMappings) //2 遍历handlerMappings定位RouterFunctionMapping
.concatMap(mapping -> mapping.getHandler(exchange)) // 3.获取HandlerFunction
.next()
.switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
.flatMap(handler -> invokeHandler(exchange, handler)) //4.执行
.flatMap(result -> handleResult(exchange, result)); //5. 处理结果
}
上面的代码已经把大部分的流程说明清楚了,那么我们来看一下lambda
表达式中每个内部方法的具体实现。
首先我们来看一下步骤3的具体实现org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
//调用 getHandlerInternal 方法来确定HandlerFunction
return getHandlerInternal(exchange).map(handler -> {
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
CorsConfiguration configB = getCorsConfiguration(handler, exchange);
CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
if (!getCorsProcessor().process(config, exchange) ||
CorsUtils.isPreFlightRequest(exchange.getRequest())) {
return REQUEST_HANDLED_HANDLER;
}
}
return handler;
});
}
上面一大段代码其实主要来获取handler
的方法是getHandlerInternal(exchange)
剩下的部分是跨域处理的逻辑。我们看一下这个方法。
@Override
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
if (this.routerFunction != null) {
ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
exchange.getAttributes().put(RouterFunctions.REQUEST_ATTRIBUTE, request);
return this.routerFunction.route(request); //通过路由获取到对应处理的HandlerFunction也就是执行方法
} else {
return Mono.empty();
}
}
获取到对应的HandlerFunction
后我们就来执行第四步,调用HandlerFunction
。
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
if (this.handlerAdapters != null) {
for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
if (handlerAdapter.supports(handler)) { //判断HandlerAdapters中是否支持之前获取到的handler
return handlerAdapter.handle(exchange, handler); //执行handler 对应下面handle的方法
}
}
}
return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));
}
org.springframework.web.reactive.function.server.support.HandlerFunctionAdapter#handle
方法,这个类中的方法就是处理函数式端点请求的Adapter
具体实现
@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
ServerRequest request = exchange.getRequiredAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
return handlerFunction.handle(request) //由lambda模式 (返回值-参数) 无需准确的方法签名
.map(response -> new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE));
}
这里的lambda模式比较难理解,主要是看HandlerFunction
这个函数式接口
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
/**
* Handle the given request.
* @param request the request to handle
* @return the response
*/
Mono<T> handle(ServerRequest request);
}
我们只需要满足 入参是ServerRequest
类型 返回值是Mono<T>
就可以执行。
调用完具体方法之后,我们就可以进行返回值解析序列化了。这里就是步骤5 处理结果。
private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
//获取对应的返回结果处理器并处理
return getResultHandler(result).handleResult(exchange, result)
//如果出现错误或者异常 则选择对应的异常结果处理器进行处理
.onErrorResume(ex -> result.applyExceptionHandler(ex)
.flatMap(exceptionResult -> getResultHandler(exceptionResult)
.handleResult(exchange, exceptionResult)));
}
我们再来看一下getResultHandler
代码
private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {
if (this.resultHandlers != null) {
for (HandlerResultHandler resultHandler : this.resultHandlers) {
if (resultHandler.supports(handlerResult)) {
return resultHandler;
}
}
}
throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());
}
在这里我们看一下resultHandlers
中都含有哪些返回值处理器
我们通过截图可以看出返回值解析器跟流程图一一对应。
在匹配到对应的返回值解析器之后进行返回值的封装和写会,这里要注意DataBuffer
是NIO的写处理,最后写回到浏览器客户端。
三、实战
3.1 添加webflux依赖
新建一个Spring Boot
项目,在pom.xml
文件中添加webflux
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
3.1.1 定义接口
新建一个controller
包,用来放置对外的接口类,再创建一个 HelloWebFluxController.class
类,定义两个接口:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import site.exception.springbootwebfluxhello.entity.User;
@RestController
public class HelloWebFluxController {
@GetMapping("/hello")
public String hello() {
return "Hello, WebFlux !";
}
@GetMapping("/user")
public Mono<User> getUser() {
User user = new User();
user.setName("name");
user.setDesc("测试webFlux");
return Mono.just(user);
}
}
User.java:
@data
public class User {
/**
* 姓名
*/
private String name;
/**
* 描述
*/
private String desc;
}
以上控制器类中,我们使用的全都是Spring MVC
的注解,分别定义了两个接口:
- 一个
GET
请求的/hello
接口,返回Hello, WebFlux!
字符串。 - 又定义了一个
GET
请求的/user
方法,返回的是JSON格式User
对象。
这里注意,User
对象是通过Mono
对象包装的,你可能会问,为啥不直接返回呢?
在WebFlux
中,Mono
是非阻塞的写法,只有这样,你才能发挥WebFlux
非阻塞 + 异步的特性。
补充:在WebFlux中,除了
Mono
外,还有一个Flux
,这哥俩均能充当响应式编程中发布者的角色,不同的是:
Mono
:返回0
或1
个元素,即单个对象。Flux
:返回N
个元素,即List
列表对象。
3.1.2 Mono
Mono
是什么?官方描述如下:
A Reactive Streams Publisher with basic rx operators that completes successfully by emitting an element, or with an error.
Mono
是响应流Publisher
具有基础rx
操作符。可以成功发布元素或者错误。如图所示:
Mono
常用的方法有:
- Mono.create():使用
MonoSink
来创建Mono
- Mono.justOrEmpty():从一个
Optional
对象或null
对象中创建Mono
。 - Mono.error():创建一个只包含错误消息的
Mono
- Mono.never():创建一个不包含任何消息通知的
Mono
- Mono.delay():在指定的延迟时间之后,创建一个
Mono
,产生数字0
作为唯一值
3.1.3 Flux
Flux
是什么?官方描述如下:
A Reactive Streams Publisher with rx operators that emits 0 to N elements, and then completes (successfully or with an error).
Flux
是响应流Publisher
具有基础rx
操作符。可以成功发布0
到N
个元素或者错误。Flux
其实是Mono
的一个补充。如图所示:
所以要注意:如果知道Publisher
是0
或1
个,则用Mono
。
Flux
最值得一提的是fromIterable
方法。fromIterable(Iterable<? extends T> it)
可以发布Iterable
类型的元素。当然,Flux
也包含了基础的操作:map
、merge
、concat
、flatMap
、take
,这里就不展开介绍了。
3.2 使用配置模式进行WebFlux接口开发
- 可以编写一个处理器类
Handler
代替Controller
,Service
、dao
层保持不变。 - 配置请求的路由
3.2.1 处理器类Handler
处理器类Handler
需要从请求解析参数,并且封装响应,代码如下:
package com.demo.springcloud.reactive.user.info.config.handler;
import com.demo.springcloud.common.exception.BusinessException;
import com.demo.springcloud.reactive.user.info.dto.User;
import com.demo.springcloud.reactive.user.info.service.impl.JpaEntityServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Slf4j
@Component
public class UserReactiveHandler {
@Resource
private JpaEntityServiceImpl jpaEntityService;
/**
* 得到所有用户
*
* @param request
* @return
*/
public Mono<ServerResponse> getAllUser(ServerRequest request) {
log.info("方法 getAllUser 被调用了");
return ok().contentType(APPLICATION_JSON_UTF8)
.body(Flux.fromIterable(jpaEntityService.selectAllUser()), User.class);
}
/**
* 创建用户
*
* @param request
* @return
*/
public Mono<ServerResponse> createUser(ServerRequest request) {
// 2.0.0 是可以工作, 但是2.0.1 下面这个模式是会报异常
Mono<User> user = request.bodyToMono(User.class);
/**Mono 使用响应式的,时候都是一个流,是一个发布者,任何时候都不能调用发布者的订阅方法
也就是不能消费它, 最终的消费还是交给我们的Springboot来对它进行消费,任何时候不能调用它的
user.subscribe();
不能调用block
把异常放在统一的地方来处理
*/
return user.flatMap(dto -> {
// 校验代码需要放在这里
if (StringUtils.isBlank(dto.getName())) {
throw new BusinessException("用户名不能为空");
}
return ok().contentType(APPLICATION_JSON_UTF8).body(
Mono.create(cityMonoSink ->
cityMonoSink.success(jpaEntityService.addUser(dto))), User.class);
});
}
/**
* 根据id删除用户
*
* @param request
* @return
*/
public Mono<ServerResponse> deleteUserById(ServerRequest request) {
String id = request.pathVariable("id");
// 校验代码需要放在这里
if (StringUtils.isBlank(id)) {
throw new BusinessException("id不能为空");
}
User dto = new User();
dto.setUserId(Long.parseLong(id));
return ok().contentType(APPLICATION_JSON_UTF8).body(
Mono.create(cityMonoSink ->
cityMonoSink.success(jpaEntityService.delUser(dto))), User.class);
}
}
3.2.2 路由配置
package com.demo.springcloud.reactive.user.info.config;
import com.demo.springcloud.reactive.user.info.config.handler.UserReactiveHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
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 org.springframework.web.server.WebFilter;
import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
@Configuration
public class RoutersConfig {
@Bean
RouterFunction<ServerResponse> routes(UserReactiveHandler handler) {
// 下面的相当于类里面的 @RequestMapping
// 得到所有用户
return RouterFunctions.route(GET("/user"), handler::getAllUser)
// 创建用户
.andRoute(POST("/user").and(
accept(MediaType.APPLICATION_JSON_UTF8)), handler::createUser)
// 删除用户
.andRoute(DELETE("/user/{id}"), handler::deleteUserById);
}
@Value("${server.servlet.context-path}")
private String contextPath;
//处理上下文路径,没有上下文路径,此函数可以忽略
@Bean
public WebFilter contextPathWebFilter() {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String requestPath = request.getURI().getPath();
if (requestPath.startsWith(contextPath)) {
return chain.filter(
exchange.mutate()
.request(request.mutate().contextPath(contextPath).build())
.build());
}
return chain.filter(exchange);
};
}
}
3.3 测试
3.3.1 配置模式的WebFlux Rest接口测试
配置模式的WebFlux Rest
接口只能使用PostMan
测试,例子如下:
注意,不能带上下文路径:http://192.168.68.1:7705/uaa-react-provider/user
3.3.2 注解模式的WebFlux Rest接口测试
3.3.2.1 swagger增加界面
CRUD其他的界面,略过
3.4 配置大全
3.4.1 静态资源配置
@Configuration
@EnableWebFlux //使用注解@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer { //继承WebFluxConfigurer
//配置静态资源
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/file/**")
.addResourceLocations("file:" + System.getProperty("user.dir")
+ File.separator + "file" + File.separator);
registry.addResourceHandler("/swagger-ui.html**")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
//配置拦截器
//配置编解码
//...
}
3.4.2 WebFluxSecurity配置
@Configuration
@EnableWebSecurity
public class WebMvcSecurityConfig extends WebSecurityConfigurerAdapter implements
AuthenticationEntryPoint, //未验证回调
AuthenticationSuccessHandler, //验证成功回调
AuthenticationFailureHandler, //验证失败回调
LogoutSuccessHandler { //登出成功回调
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {
sendJson(response, new Response<>(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"));
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
sendJson(response, new Response<>(1, "Incorrect"));
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
sendJson(response, new Response<>(0, authentication.getClass().getSimpleName()));
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
sendJson(response, new Response<>(0, "Success"));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/swagger*/**", "/webjars/**", "/v2/api-docs")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/static/**", "/file/**")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.logout()
.logoutUrl("/user/logout") //虚拟路径,不是控制器定义的路径
.logoutSuccessHandler(this)
.permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(this)
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/user/login") //虚拟路径,不是控制器定义的路径
.successForwardUrl("/user/login") //是控制器定义的路径
.failureHandler(this)
.and()
.httpBasic()
.authenticationEntryPoint(this);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
}
webflux
-验证依赖于用户数据服务,需定义实现ReactiveUserDetailsService
的Bean
@Configuration
@EnableWebFluxSecurity //使用注解@EnableWebFluxSecurity
public class WebFluxSecurityConfig implements
WebFilter, //拦截器
ServerLogoutSuccessHandler, //登出成功回调
ServerAuthenticationEntryPoint, //验证入口
ServerAuthenticationFailureHandler, //验证成功回调
ServerAuthenticationSuccessHandler { //验证失败回调
//实现接口的方法
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
//配置webflux的context-path
ServerHttpRequest request = exchange.getRequest();
if (request.getURI().getPath().startsWith(contextPath)) {
exchange = exchange.mutate()
.request(request.mutate().contextPath(contextPath).build()).build();
}
//把查询参数转移到FormData中,不然验证过滤器(ServerFormLoginAuthenticationConverter)接受不到参数
if (exchange.getRequest().getMethod() == HttpMethod.POST
&& exchange.getRequest().getQueryParams().size() > 0) {
ServerWebExchange finalExchange = exchange;
ServerWebExchange realExchange = new Decorator(exchange) {
@Override
public Mono<MultiValueMap<String, String>> getFormData() {
return super.getFormData().map(
new Function<MultiValueMap<String, String>,
MultiValueMap<String, String>>() {
@Override
public MultiValueMap<String, String> apply(
MultiValueMap<String, String> stringStringMultiValueMap) {
if (stringStringMultiValueMap.size() == 0) {
return finalExchange.getRequest().getQueryParams();
} else {
return stringStringMultiValueMap;
}
}
});
}
};
return chain.filter(realExchange);
}
return chain.filter(exchange);
}
@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange webFilterExchange,
Authentication authentication) {
return sendJson(webFilterExchange.getExchange(), new Response<>("登出成功"));
}
@Override
public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
return sendJson(exchange, new Response<>(HttpStatus.UNAUTHORIZED.value(), "未验证"));
}
@Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
AuthenticationException exception) {
return sendJson(webFilterExchange.getExchange(), new Response<>(1, "验证失败"));
}
@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange,
Authentication authentication) {
return webFilterExchange.getChain().filter(
webFilterExchange.getExchange().mutate()
.request(t -> t.method(HttpMethod.POST).path("/user/login")) //转发到自定义控制器
.build()
);
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.addFilterAfter(this, SecurityWebFiltersOrder.FIRST)
.csrf().disable()
.authorizeExchange()
.pathMatchers("/swagger*/**", "/webjars/**", "/v2/api-docs") //swagger
.permitAll()
.and()
.authorizeExchange()
.pathMatchers("/static/**", "/file/**") //静态资源
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.logout() //登出
.logoutUrl("/user/logout")
.logoutSuccessHandler(this)
.and()
.exceptionHandling() //未验证回调
.authenticationEntryPoint(this)
.and()
.formLogin()
.loginPage("/user/login")
.authenticationFailureHandler(this) //验证失败回调
.authenticationSuccessHandler(this) //验证成功回调
.and()
.httpBasic()
.authenticationEntryPoint(this); //basic验证,一般用于移动端
return http.build();
}
}
3.4.3 WebSession配置
@Configuration
@EnableRedisWebSession(maxInactiveIntervalInSeconds = 60)
//使用注解@EnableRedisWebSession ,maxInactiveIntervalInSeconds设置数据过期时间,spring.session.timeout不管用
public class RedisWebSessionConfig { //考虑到分布式系统,一般使用redis存储session
@Bean
public LettuceConnectionFactory lettuceConnectionFactory() {
return new LettuceConnectionFactory();
}
}
//单点登录使用ReactiveRedisSessionRepository.getSessionRedisOperations().scan方法查询相同用户名的session,
// 删除其他session即可
public Mono<Map<String, String>> findByPrincipalName(String name) {
return reactiveSessionRepository.getSessionRedisOperations().scan(
ScanOptions.scanOptions().match(
ReactiveRedisSessionRepository.DEFAULT_NAMESPACE + ":sessions:*").build())
.flatMap(new Function<String, Publisher<Tuple2<String, Map.Entry<Object, Object>>>>() {
@Override
public Publisher<Tuple2<String, Map.Entry<Object, Object>>> apply(String s) {
return reactiveSessionRepository.getSessionRedisOperations().opsForHash()
.entries(s).map(
new Function<Map.Entry<Object, Object>,
Tuple2<String, Map.Entry<Object, Object>>>() {
@Override
public Tuple2<String, Map.Entry<Object, Object>> apply(
Map.Entry<Object, Object> objectObjectEntry) {
return Tuples.of(s, objectObjectEntry);
}
});
}
})
.filter(new Predicate<Tuple2<String, Map.Entry<Object, Object>>>() {
@Override
public boolean test(Tuple2<String, Map.Entry<Object, Object>> rule) {
Map.Entry<Object, Object> t = rule.getT2();
String key = "sessionAttr:" +
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
if (key.equals(t.getKey())) {
User sci = (User) ((SecurityContextImpl) t.getValue())
.getAuthentication().getPrincipal();
return sci.getUsername().equals(name);
}
return false;
}
})
.collectMap(new Function<Tuple2<String, Map.Entry<Object, Object>>, String>() {
@Override
public String apply(Tuple2<String, Map.Entry<Object, Object>> rule) {
return name;
}
}, new Function<Tuple2<String, Map.Entry<Object, Object>>, String>() {
@Override
public String apply(Tuple2<String, Map.Entry<Object, Object>> rule) {
return rule.getT1().replace(ReactiveRedisSessionRepository.DEFAULT_NAMESPACE
+ ":sessions:", "");
}
});
}
}
}
对标的SpringWebMVC
配置
@Configuration
@EnableRedisHttpSession //使用注解@EnableRedisHttpSession
public class RedisHttpSessionConfig { //考虑到分布式系统,一般使用redis存储session
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
}
//单点登录使用FindByIndexNameSessionRepository根据用户名查询session,删除其他session即可
Map<String, Session> map = findByIndexNameSessionRepository.findByPrincipalName(name);
3.4.4 文件上传配置
//参数上传
//定义参数bean
@Setter
@Getter
@ToString
@ApiModel
public class QueryBean {
@ApiModelProperty(value = "普通参数", required = false, example = "")
private String query;
@ApiModelProperty(value = "文件参数", required = false, example = "")
private FilePart image; //强调,webflux中使用FilePart作为接收文件的类型
}
//定义接口
@ApiOperation("一个接口")
@PostMapping("/path")
//这里需要使用@ApiImplicitParam显示配置【文件参数】才能使swagger界面显示上传文件按钮
@ApiImplicitParams({
@ApiImplicitParam(
paramType = "form", //表单参数
dataType = "__file", //最新版本使用__file表示文件,以前用的是file
name = "image", //和QueryBean里面的【文件参数image】同名
value = "文件") //注释
})
public Mono<Response> bannerAddOrUpdate(QueryBean q) {
}
3.5 WebFlux执行流程
userAdd方法代码如下:
public Mono<User> userAdd(@RequestBody User dto) {
//命令式写法
//jpaEntityService.delUser(dto);
//响应式写法
return Mono.create(cityMonoSink -> cityMonoSink.success(jpaEntityService.addUser(dto)));
}
由于返回的数据只有一个所以使用的是Mono
作为返回数据,使用Mono
类静态create
方法创建Mono
对象,代码如下:
public abstract class Mono<T> implements Publisher<T> {
static final BiPredicate EQUALS_BIPREDICATE = Object::equals;
public Mono() {
}
public static <T> Mono<T> create(Consumer<MonoSink<T>> callback) {
return onAssembly(new MonoCreate(callback));
}
//...
}
可以到create
方法接收一个参数,参数是Consumer
对象,通过callback
可以看出,这里使用的是callback
回调,下面看看Consumer
接口的定义:
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> {
accept(t);
after.accept(t);
};
}
}
通过上面的代码可以看出,有两个方法,一个是默认的方法andThen
,还有一个accept
方法,Mono.create()
方法的参数需要一个实现类,实现Consumer
接口;Mono.create
方法的参数指向的实例对象, 就是要实现这个accept
方法。
例子中,下面的lambda
表达式,就是accept
方法的实现,实参的类型为Consumer<MonoSink>
, accept
的实现为 如下:
cityMonoSink -> cityMonoSink.success(jpaEntityService.addUser(dto))
来来来,重复看一下,create方法的实现:
public static <T> Mono<T> create(Consumer<MonoSink<T>> callback) {
return onAssembly(new MonoCreate(callback));
}
在方法内部调用了onAssembly
方法,参数是MonoCreate
对象,然后我们看看MonoCreate
类,代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package reactor.core.publisher;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Consumer;
import java.util.function.LongConsumer;
import reactor.core.CoreSubscriber;
import reactor.core.Disposable;
import reactor.core.Scannable.Attr;
import reactor.core.publisher.FluxCreate.SinkDisposable;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
final class MonoCreate<T> extends Mono<T> {
final Consumer<MonoSink<T>> callback;
MonoCreate(Consumer<MonoSink<T>> callback) {
this.callback = callback;
}
public void subscribe(CoreSubscriber<? super T> actual) {
MonoCreate.DefaultMonoSink<T> emitter = new MonoCreate.DefaultMonoSink(actual);
actual.onSubscribe(emitter);
try {
this.callback.accept(emitter);
} catch (Throwable var4) {
emitter.error(Operators.onOperatorError(var4, actual.currentContext()));
}
}
static final class DefaultMonoSink<T>
extends AtomicBoolean
implements MonoSink<T>, InnerProducer<T> {
final CoreSubscriber<? super T> actual;
volatile Disposable disposable;
static final AtomicReferenceFieldUpdater<MonoCreate.DefaultMonoSink, Disposable> DISPOSABLE =
AtomicReferenceFieldUpdater.newUpdater(MonoCreate.DefaultMonoSink.class,
Disposable.class, "disposable");
volatile int state;
static final AtomicIntegerFieldUpdater<MonoCreate.DefaultMonoSink> STATE =
AtomicIntegerFieldUpdater.newUpdater(MonoCreate.DefaultMonoSink.class, "state");
volatile LongConsumer requestConsumer;
static final AtomicReferenceFieldUpdater<MonoCreate.DefaultMonoSink, LongConsumer>
REQUEST_CONSUMER =
AtomicReferenceFieldUpdater.newUpdater(MonoCreate.DefaultMonoSink.class,
LongConsumer.class, "requestConsumer");
T value;
static final int NO_REQUEST_HAS_VALUE = 1;
static final int HAS_REQUEST_NO_VALUE = 2;
static final int HAS_REQUEST_HAS_VALUE = 3;
DefaultMonoSink(CoreSubscriber<? super T> actual) {
this.actual = actual;
}
public Context currentContext() {
return this.actual.currentContext();
}
@Nullable
public Object scanUnsafe(Attr key) {
if (key != Attr.TERMINATED) {
return key == Attr.CANCELLED
? OperatorDisposables.isDisposed(this.disposable)
: super.scanUnsafe(key);
} else {
return this.state == 3 || this.state == 1;
}
}
public void success() {
if (STATE.getAndSet(this, 3) != 3) {
try {
this.actual.onComplete();
} finally {
this.disposeResource(false);
}
}
}
public void success(@Nullable T value) {
if (value == null) {
this.success();
} else {
int s;
do {
s = this.state;
if (s == 3 || s == 1) {
Operators.onNextDropped(value, this.actual.currentContext());
return;
}
if (s == 2) {
if (STATE.compareAndSet(this, s, 3)) {
try {
this.actual.onNext(value);
this.actual.onComplete();
} finally {
this.disposeResource(false);
}
}
return;
}
this.value = value;
} while (!STATE.compareAndSet(this, s, 1));
}
}
public void error(Throwable e) {
if (STATE.getAndSet(this, 3) != 3) {
try {
this.actual.onError(e);
} finally {
this.disposeResource(false);
}
} else {
Operators.onOperatorError(e, this.actual.currentContext());
}
}
public MonoSink<T> onRequest(LongConsumer consumer) {
Objects.requireNonNull(consumer, "onRequest");
if (!REQUEST_CONSUMER.compareAndSet(this, (Object) null, consumer)) {
throw new IllegalStateException("A consumer has already been assigned" +
" to consume requests");
} else {
return this;
}
}
public CoreSubscriber<? super T> actual() {
return this.actual;
}
public MonoSink<T> onCancel(Disposable d) {
Objects.requireNonNull(d, "onCancel");
SinkDisposable sd = new SinkDisposable((Disposable) null, d);
if (!DISPOSABLE.compareAndSet(this, (Object) null, sd)) {
Disposable c = this.disposable;
if (c instanceof SinkDisposable) {
SinkDisposable current = (SinkDisposable) c;
if (current.onCancel == null) {
current.onCancel = d;
} else {
d.dispose();
}
}
}
return this;
}
public MonoSink<T> onDispose(Disposable d) {
Objects.requireNonNull(d, "onDispose");
SinkDisposable sd = new SinkDisposable(d, (Disposable) null);
if (!DISPOSABLE.compareAndSet(this, (Object) null, sd)) {
Disposable c = this.disposable;
if (c instanceof SinkDisposable) {
SinkDisposable current = (SinkDisposable) c;
if (current.disposable == null) {
current.disposable = d;
} else {
d.dispose();
}
}
}
return this;
}
public void request(long n) {
if (Operators.validate(n)) {
LongConsumer consumer = this.requestConsumer;
if (consumer != null) {
consumer.accept(n);
}
int s;
do {
s = this.state;
if (s == 2 || s == 3) {
return;
}
if (s == 1) {
if (STATE.compareAndSet(this, s, 3)) {
try {
this.actual.onNext(this.value);
this.actual.onComplete();
} finally {
this.disposeResource(false);
}
}
return;
}
} while (!STATE.compareAndSet(this, s, 2));
}
}
public void cancel() {
if (STATE.getAndSet(this, 3) != 3) {
this.value = null;
this.disposeResource(true);
}
}
void disposeResource(boolean isCancel) {
Disposable d = this.disposable;
if (d != OperatorDisposables.DISPOSED) {
d = (Disposable) DISPOSABLE.getAndSet(this, OperatorDisposables.DISPOSED);
if (d != null && d != OperatorDisposables.DISPOSED) {
if (isCancel && d instanceof SinkDisposable) {
((SinkDisposable) d).cancel();
}
d.dispose();
}
}
}
}
}
上面的代码比较多,我们主要关注下面两个函数:
MonoCreate(Consumer<MonoSink<T>> callback) {
this.callback = callback;
}
public void subscribe(CoreSubscriber<? super T> actual) {
MonoCreate.DefaultMonoSink<T> emitter = new MonoCreate.DefaultMonoSink(actual);
actual.onSubscribe(emitter);
try {
this.callback.accept(emitter);
} catch (Throwable var4) {
emitter.error(Operators.onOperatorError(var4, actual.currentContext()));
}
}
通过上面的代码可以看出,一个是构造器,参数是Consumer
,里面进行操作保存了Consumer
对象,然后在subscribe
方法里面有一句代码是this.callback.accept(emitter)
,就是在这里进行了接口的回调,回调Consumer
的accept
方法,这个方法是在调用Mono.create()
方法的时候实现了。然后在细看subscribe
方法,这里面有一个actual.onSubscribe
方法,通过方法名可以知道,这里是订阅了消息。webflux
是基于reactor
模型,基于事件消息和异步,这里也体现了一个异步。
Mono
和Flux
的其他用法可以参照上面的源码流程自己看看,就不细说了。