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 ...
随机推荐
- Hive中Join的原理和机制
转自:http://lxw1234.com/archives/2015/06/313.htm 笼统的说,Hive中的Join可分为Common Join(Reduce阶段完成join)和Map Joi ...
- 为什么「margin:auto」可以让块级元素水平居中?
知乎链接:http://www.zhihu.com/question/21644198 关于BFC的解释:W3CFans http://www.w3cfuns.com/thread-5595727-1 ...
- 关于ConcurrentSkipListMap的理解
一.前言 JCIP 提到了在 Java 6 中引入了两个新的并发集合类 ConcurrentSkipListMap 和 ConcurrentSkipListSet.其实只要介绍一下 Concurren ...
- 【转】mysql 中int类型字段unsigned和signed的区别
转自https://www.cnblogs.com/wangzhongqiu/p/6424827.html 用法: mysql> CREATE TABLE t ( a INT UNSIGNED, ...
- SSM-SpringMVC-02:SpringMVC最简单的小案例
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 咱们这个案例做什么? 就是用处理器代替Servlet处理请求 开发步骤: 1.引入jar包 <!--单 ...
- Centos7 升级 gcc
特别蛋疼的开始 最痛苦的就是一步一个坑 为了安装 vue.js,听说要安装 node.js,听说为了安装 node.js碰上了gcc版本不够的问题,此时我特别特别特别的想念盖茨大大 下载 gcc gc ...
- CentOS6.7 mysql5.6.33修改数据文件位置
问题:mysql存放的数据文件,分区容量较小,目前已经满,导致mysql连接不上, 解决方案: 1.删除分区里一个不需要用的数据,如:日志文件等(解决不了根本问题) 2.对某个磁盘扩容 3.修改数据存 ...
- Python并发编程之从生成器使用入门协程(七)
大家好,并发编程 进入第七篇. 从今天开始,我们将开始进入Python的难点,那就是协程. 为了写明白协程的知识点,我查阅了网上的很多相关资料.发现很难有一个讲得系统,讲得全面的文章,导致我们在学习的 ...
- adb常用操作命令
1.adb简介: adb,即 Android Debug Bridge.通过这个工具和android进行交互操作 2.adb命令格式: adb [-d|-e|-s <serialNu ...
- [Python接口自动化]从零开始学习python自动化(1):环境搭建
第一步:安装python编译环境 安装python编译环境之前,必须保证已安装jdk哈,如果为安装,请参考https://jingyan.baidu.com/article/6dad5075d1dc4 ...