spring boot整合spring5-webflux从0开始的实战及源码解析
上篇文章<你的响应阻塞了没有?--Spring-WebFlux源码分析>介绍了spring5.0 新出来的异步非阻塞服务,很多读者说太理论了,太单调了,这次我们就通过一个从0开始的实例实战一下。
1.准备工作
spring 提供的IDE工STS,配置好maven即可
2.创建spring boot start项目spring5-webflux,并添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring5-webflux</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring5-webflux</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
3.增加处理器HelloWorldHandler
package com.example.demo; import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; @Component
public class HelloWorldHandler { public Mono<ServerResponse> helloWorld(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World!"));
}
}
4.增加路由器,对应HandlerFunction
package com.example.demo; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse; @Configuration
public class HelloWorldRouter { @Bean
public RouterFunction<ServerResponse> routeHelloWorld(HelloWorldHandler helloWorldHandler) { return RouterFunctions
.route(RequestPredicates.GET("/helloWorld").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), helloWorldHandler::helloWorld);
}
}
默认启动时,提供了DefaultRouterFunction的实例
/**
* Route to the given handler function if the given request predicate applies.
* <p>For instance, the following example routes GET requests for "/user" to the
* {@code listUsers} method in {@code userController}:
* <pre class="code">
* RouterFunction<ServerResponse> route =
* RouterFunctions.route(RequestPredicates.GET("/user"), userController::listUsers);
* </pre>
* @param predicate the predicate to test
* @param handlerFunction the handler function to route to if the predicate applies
* @param <T> the type of response returned by the handler function
* @return a router function that routes to {@code handlerFunction} if
* {@code predicate} evaluates to {@code true}
* @see RequestPredicates
*/
public static <T extends ServerResponse> RouterFunction<T> route(
RequestPredicate predicate, HandlerFunction<T> handlerFunction) { return new DefaultRouterFunction<>(predicate, handlerFunction);
}
5.启动spring boot项目,调试进入DispatchHandler
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
if (this.handlerMappings == null) {
return createNotFoundError();
}
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange))
.next()
.switchIfEmpty(createNotFoundError())
.flatMap(handler -> invokeHandler(exchange, handler))
.flatMap(result -> handleResult(exchange, result));
}
此时HandlerMapping已经初始化完成

5.1 获取Handler
AbstractHandlerMapping.java
@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
return getHandlerInternal(exchange).map(handler -> {
if (logger.isDebugEnabled()) {
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
}
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
CorsConfiguration configA = this.corsConfigurationSource.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;
});
}
通过RouterFunctions获取HandlerAdapter

5.2 执行HandlerAdapter

HelloWorldHandler的类型是HandlerFunctionAdapter类型,触发HandlerFunctionAdapter执行handle方法
@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
HandlerFunction<?> handlerFunction = (HandlerFunction<?>) handler;
ServerRequest request = exchange.getRequiredAttribute(RouterFunctions.REQUEST_ATTRIBUTE);
return handlerFunction.handle(request)
.map(response -> new HandlerResult(handlerFunction, response, HANDLER_FUNCTION_RETURN_TYPE));
}
最后调用HelloWorldHandler的helloWorld方法
@Component
public class HelloWorldHandler { public Mono<ServerResponse> helloWorld(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello World!"));
}
}
5.3 执行HandleResult
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)));
}

因HelloWorldRouter返回结果类型是ServerResponse,故调用ServerResponseResultHandler来处理结果
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
ServerResponse response = (ServerResponse) result.getReturnValue();
Assert.state(response != null, "No ServerResponse");
return response.writeTo(exchange, new ServerResponse.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return messageWriters;
}
@Override
public List<ViewResolver> viewResolvers() {
return viewResolvers;
}
});
}
6.总结
spring mvc和spring webflux是一对兄弟,他们的处理流程类似,mvc是同步阻塞服务,webflux是异步非阻塞服务,它们直接的关系如下:

spring webflux 增加了functional endpoint,RouterFunction(RouterFunctions构建)等同于HanderMapping, HandlerFunction(HandlerFunctionAdapter继承自HandlerAdapter来代理)等同于HandlerAdapter
spring webflux 引入了spring boot2,Reactor,lambda表达式,语法更简洁,但可读性变弱。
参考文献:
【1】https://www.journaldev.com/20763/spring-webflux-reactive-programming
spring boot整合spring5-webflux从0开始的实战及源码解析的更多相关文章
- AFNetworking 3.0 使用详解 和 源码解析实现原理
AFN原理&& AFN如何使用RunLoop来实现的: 让你介绍一下AFN源码的理解,首先要说说封装里面主要做了那些重要的事情,有那些重要的类(XY题) 一.AFN的实现步骤: NSS ...
- Spring笔记(2) - 生命周期/属性赋值/自动装配及部分源码解析
一.生命周期 @Bean自定义初始化和销毁方法 //====xml方式: init-method和destroy-method==== <bean id="person" c ...
- Spring Boot入门,源码解析
目录 1.Spring Boot简介 2.微服务 3.Spring Boot HelloWorld 3.1 创建一个Maven工程 3.2 导入依赖Spring Boot相关的依赖 3.3 编写一个主 ...
- Spring系列(三):Spring IoC源码解析
一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...
- spring boot 2.x 系列 —— spring boot 整合 servlet 3.0
文章目录 一.说明 1.1 项目结构说明 1.2 项目依赖 二.采用spring 注册方式整合 servlet 2.1 新建过滤器.监听器和servlet 2.2 注册过滤器.监听器和servlet ...
- Spring Boot 整合 Elasticsearch,实现 function score query 权重分查询
摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 预见未来最好的方式就是亲手创造未来 – <史蒂夫·乔布斯传> 』 运行环境: ...
- Spring Kafka和Spring Boot整合实现消息发送与消费简单案例
本文主要分享下Spring Boot和Spring Kafka如何配置整合,实现发送和接收来自Spring Kafka的消息. 先前我已经分享了Kafka的基本介绍与集群环境搭建方法.关于Kafka的 ...
- Spring Boot整合Mybatis并完成CRUD操作
MyBatis 是一款优秀的持久层框架,被各大互联网公司使用,本文使用Spring Boot整合Mybatis,并完成CRUD操作. 为什么要使用Mybatis?我们需要掌握Mybatis吗? 说的官 ...
- Spring Boot整合Elasticsearch
Spring Boot整合Elasticsearch Elasticsearch是一个全文搜索引擎,专门用于处理大型数据集.根据描述,自然而然使用它来存储和搜索应用程序日志.与Logstash和K ...
随机推荐
- spring-mvc报红错误
can't resolve spring 以及运行测试案例报错 ‘无法找到路径’ 解决办法是在pom文件里面重新配置 对应 resource <resources> <resou ...
- python中@classmethod @staticmethod区别
Python中3种方式定义类方法, 常规方式, @classmethod修饰方式, @staticmethod修饰方式. class A(object): def foo(self, x): prin ...
- CSS(选择器)
CSS(选择器) 作用:用于匹配 HTML 元素 选择器分类: 1.元素选择器 a{} 2.伪元素选择器 ::before{} (真实存在的元素) 3.类选择器 .link{} 4.属性选择 ...
- C语言下double转char*或者std::string,可以精确转换不含多余的0
char* GetDoubleStr(double value) { char buf[32]={0};//长度可以自定义 sprintf(buf,"%.8f",value);// ...
- javascript中的隐式类型转化
javascript中的隐式类型转化 #隐式转换 ## "+" 字符串和数字 如果某个操作数是字符串或者能够通过以下步骤转换为字符串的话,+将进行拼接操作. 如果其中一个操作数是对 ...
- 二十三、Hadoop学记笔记————Spark简介与计算模型
spark优势在于基于内存计算,速度很快,计算的中间结果也缓存在内存,同时spark也支持streaming流运算和sql运算 Mesos是资源管理框架,作为资源管理和任务调度,类似Hadoop中的Y ...
- ARM-Linux中断系统
1.前言 了解Linux中断子系统,同时也需要了解ARM体系结构中断处理流程:在熟悉整个软硬件架构和流程基础上,才能对流程进行细化,然后找出问题的瓶颈.<2. 梳理中断处理子系统> 但是所 ...
- Linux kernel的中断子系统之(八):softirq
返回目录:<ARM-Linux中断系统>. 总结:中断分为上半部和下半部,上半部关中断:下半部开中断,处理可以延迟的事情.下半部有workqueue/softirq/tasklet三种方式 ...
- 在 Docker 容器中运行应用程序
案例说明 运行 3 个容器,实现对网站的监控. 三个容器的说明: 容器 web: 创建自 nginx 映像,使用 80 端口,运行于后台,实现 web 服务. 容器 mailer: 该容器中运行一个 ...
- Whitelabel Error Page 专题
Spring boot为错误视图提供了如下错误属性:timestamp:错误发生的时间status:HTTP状态码error:错误原因exception:异常的类名message:异常消息(如果这个错 ...