再谈SpringCloud Gateway源码
再谈SpringCloud Gateway源码
- 一、整体请求流程
- 二、前置对象准备
- 1、实例化HandlerMapping
- 2、实例化Route
- 3、实例化WebHandler
- 三、实践业务扩展点
- 1、定义扩展Route对象
- 2、Filter能做什么
- 3、定义扩展Filter对象
- 4、定义父类Filter简化请求参数处理
前言:
之前有阅读过一次SpringCloud Gateway的源码,不过那时更多的是浮于表面,走了一遍流程。直到现在工作中真的遇到了基于SpringCloud Gateway的业务开发,才发现源码中很多机制还是不熟悉,于时又重新学习了一遍源码,并做此记录
前置知识:
- Reactive Streams&Reactor Core
- Spring WebFlux
- 浅谈Spring Cloud Gateway源码
一、整体请求流程
SpringCloud Gateway是基于Spring WebFlux完成请求的分发,整合SpringCloud Gateway后请求分发流程如下图:
二、前置对象准备
上面的流程更多的是描述当一个请求进来以后的分发流程,但是涉及到的一些对象都是在项目启动阶段完成的实例化
1、实例化HandlerMapping
在SpringCloud Gateway中会进行自动装配,自动创建一个RoutePredicateHandlerMapping,用于后续处理请求。当然我们也可以自定义,只要顺序在他的前面就可以(这也是常见自定义HandlerMapping的操作)
2、实例化Route
该部分逻辑涉及实例化对象较多
在HandlerMapping中,会涉及当前请求url与Route中定义的请求url匹配的逻辑,匹配命中则代表是网关请求,如果无法匹配命中则继续使用后续的handlerMapping判定,如当前项目上的创建的controller的接口的HandlerMapping。
1)RouteDefinitionLocator:实例化CompositeRouteDefinitionLocator
CompositeRouteDefinitionLocator可以看做是RouteDefinitionLocator的包装器,用于遍历访问所有RouteDefinitionLocator中定义的RouteDefinition。
在使用侧,我们只需要按照RouteDefinitionLocator中RouteDefinition的标准定义对象,即可完成自定义的添加
2)RouteLocator:实例化RouteDefinitionRouteLocator
这里会借助GatewayProperties、GatewayFilterFactory、RoutePredicateFactory,以及上一步创建的CompositeRouteDefinitionLocator(内部包含所有的RouteDefinition)来实例化该对象。
请注意,由于@Primary注解的存在,Gateway中使用的RouteLocator其实是下面的CachingRouteLocator,不过在CachingRouteLocator中,只是对所有的Route进行了缓存。
值得一提的是,这里的CompositeRouteLocator和前面的CompositeRouteDefinitionLocator的作用都是一致的,都是完成对所RouteLocator的包装,尽管示例代码中只有一个,但在真实的业务场景中,我们同样可以进行自定义扩展
注意:记住前面的RouteDefinitionLocator和RouteLocator,都是被Composite包装过一次,并且逻辑都是封装遍历。明白这个对于后续业务代码的执行流程至关重要。
3)Route:实例化Route
回顾最整体的请求流程,在获取HandlerMapping的时候,会调用到getHandlerInternal方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal。再往后走会调用到lookupRoute方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute)
即下图示例位置:
根据前面的解释,调用getRoutes的逻辑会遍历所有的RouteLocator,然后在RouteLocator内部又会遍历调用所有的RouteDefinitionLocator。此时就到了最关键的convertToRoute方法。该方法就会构建所有的断路器、拦截器(实例化Bean的时候有传递对应的Factory,在此处完成构建)
最终lookupRoute的返回值是Mono<Route>对象,即会返回匹配到的第一个Route,该对象就是我们预期命中的Route
3、实例化WebHandler
在getHandlerInternal方法(org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal)中
,在命中Route路由后,只是将Route放置在了上下文中,此处真正返回的是在该HandlerMapping中定义的WebHandler对象
WebHandler对象同样是在自动装配类中实例化的,并且附带过滤器配置。
于是,我们可以得到结论,在最后传递到HandlerAdapter中执行handler请求的Object对象就是Gateway中定义的FilteringWebHandler(WebHandler)
最终在该WebHandler中,完成对应的执行逻辑。查看代码可以得出结论,这里的Filter主要分为两部分,一部分是从Route对象上获取的Filter(GatewayFilter),还有一部分是全局Filter(GlobalFilter),这部分在该WebHandler实例化的就传递进来了。将所有的Filter串联在一起,以一个责任链的形式完成业务逻辑
三、实践业务扩展点
1、定义扩展Route对象
我们可以自定义一个RouteDefinitionLocator类型的Bean,只要在调用该类的getRouteDefinitions方法时,返回我们自定义的Route,具体定义方式有很多,比如使用JSON,只需要最终解析出来的逻辑满足RouteDefinition规范即可
2、Filter能做什么
更多请参考官网文档
常见GlobalFilter:
- AdaptCachedBodyGlobalFilter:缓存
- NettyWriteResponseFilter:response写入
- ForwardPathFilter:转发请求
- ReactiveLoadBalancerClientFilter:负载均衡
- LoadBalancerServiceInstanceCookieFilter:根据cookie负载均衡
- NettyRoutingFilter:利用netty发起请求
常见GatewayFilter:
- AddRequestHeaderGatewayFilter:添加请求头
- PrefixPathGatewayFilter:切除path前缀
- RewritePathGatewayFilter:重写path
- RetryGatewayFilter:请求重试
- RemoveRequestHeaderGatewayFilter:移除请求头信息
- RequestRateLimiterGatewayFilter:限流
业务系统中常见的使用:
- 认证授权
- 基础参数校验
- 消息结构体的转化(XML转JSON)
- 业务参数级别的限流(尽管自带限流,但是功能过于简单,无法满足企业级系统的能力)
- 配置参数完成请求参数和返回结果的二次修改
- RPC的泛化调用
- 业务指标的监控埋点
- …
3、定义扩展Filter对象
如果我们希望这个Filter不用配置全局生效,就继承GlobalFilter;如果是希望在RouteDefinition中定义了才生效,则继承GatewayFilter。
补充:
- 针对GatewayFilter,我们要定义的有两部分,一个是Filter的定义AbstractGatewayFilterFactory类(Filter的Factory),需要实现apply方法用于实例化相应的GatewayFilter对象(可使用OrderedGatewayFilter对象进行包装);另一个是Filter的实现类,用于完成具体的业务逻辑。
- 执行流程为,在Route实例化的时候(convertToRoute),会根据Route中配置的Filter列表名字过滤(不修改则使用默认规则)出后所有的GatewayFilterFactory,然后调用GatewayFilterFactory的apply方法,此时就会回调到我们创建的GatewayFilter类的apply方法,此时只需要在apply方法中调用我们真正的业务Filter即可(尽管这里是Factory,但是可以参考适配器模式理解),示例代码如下:
@Component
public class RequestHandlerGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestHandlerGatewayFilterConfig> {@AutowiredRequestHandlerGatewayFilter filter;public RequestHandlerGatewayFilterFactory() {// 指定配置文件对象类型super(RequestHandlerGatewayFilterConfig.class);}@Overridepublic GatewayFilter apply(RequestHandlerGatewayFilterConfig config) {// 设置filter顺序int order = Optional.ofNullable(config).map(BaseFilterConfig::getOrder).orElse(RouteOrderConstants.REQUEST_HANDLER_ORDER);return new OrderedGatewayFilter((exchange, chain) -> {// 配置文件内容传递exchange.getAttributes().put(GatewayConstants.CACHE_REQUEST_REQUEST_HANDLER_CONFIG, config);return filter.apply(exchange, chain);}, order);}
}
4、定义父类Filter简化请求参数处理
GatewayFilter的filter方法参数为ServerWebExchange对象,这并不方便我们在业务上对请求参数处理。我们可以编写一个抽象父类,用于屏蔽掉请求参数获取这部分,示例代码可参考:
public class CacheOutputMessage implements ReactiveHttpOutputMessage {private final DataBufferFactory bufferFactory;private final HttpHeaders httpHeaders;private Flux<DataBuffer> body = Flux.error(new IllegalStateException("error"));public CacheOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {this.bufferFactory = exchange.getResponse().bufferFactory();this.httpHeaders = httpHeaders;}@Overridepublic void beforeCommit(Supplier<? extends Mono<Void>> action) {}@Overridepublic boolean isCommitted() {return false;}@Overridepublic HttpHeaders getHeaders() {return this.httpHeaders;}@Overridepublic DataBufferFactory bufferFactory() {return this.bufferFactory;}public Flux<DataBuffer> getBody() {return this.body;}@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {this.body = Flux.from(body);return Mono.empty();}@Overridepublic Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {return writeWith(Flux.from(body).flatMap(p -> p));}@Overridepublic Mono<Void> setComplete() {return writeWith(Flux.empty());}
}
public class AbstractGatewayFilter {/*** 入口:FilterFactory的回调方法*/public Mono<Void> apply(ServerWebExchange exchange, GatewayFilterChain chain) {// 如果已经完成请求,不再执行后续操作if (ServerWebExchangeUtils.isAlreadyRouted(exchange)) {return chain.filter(exchange);}// 1、修改请求体ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());Mono<String> afterModifiedBody = serverRequest.bodyToMono(String.class).defaultIfEmpty("").flatMap(body -> {BiFunction<ServerWebExchange, Mono<String>, Mono<String>> modifyBodyFun =(funExchange, funBody) ->// 修改请求内容funBody.map(b -> getModifiedBody(b, exchange));return modifyBodyFun.apply(exchange, Mono.just(body));});// 2、重新构造请求对象(含有请求头修改)return prepareOutputMessage(exchange, afterModifiedBody).flatMap(outputMessage -> chain.filter(exchange.mutate().request(decorate(exchange, outputMessage.getHeaders(), outputMessage)).build()));}private Mono<CacheOutputMessage> prepareOutputMessage(ServerWebExchange exchange, Mono<String> modifiedBody) {HttpHeaders headers = copyHeaders(exchange.getRequest().getHeaders());BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter= BodyInserters.fromPublisher(modifiedBody, String.class);CacheOutputMessage outputMessage = new CacheOutputMessage(exchange, headers);return bodyInserter.insert(outputMessage, new BodyInserterContext()).thenReturn(outputMessage);}private ServerHttpRequestDecorator decorate(ServerWebExchange exchange,HttpHeaders headers,CacheOutputMessage outputMessage) {return new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic HttpHeaders getHeaders() {return copyHeaders(getModifiedHeaders(exchange, super.getHeaders()));}@Overridepublic Flux<DataBuffer> getBody() {return outputMessage.getBody();}};}/*** 复制一份新的请求头*/private HttpHeaders copyHeaders(HttpHeaders headers) {HttpHeaders newHeaders = new HttpHeaders();if (headers == null) {return newHeaders;}Map<String, List<String>> map = new HashMap<>();headers.forEach((key, dataList) ->map.put(key,CollectionUtils.isEmpty(dataList) ? dataList : new ArrayList<>(dataList)));newHeaders.putAll(map);return newHeaders;}/*** 子类可重写此方法,用于修改请求头*/protected HttpHeaders getModifiedHeaders(ServerWebExchange exchange, HttpHeaders headers) {return headers;}/*** 子类可重写此方法,用于修改请求内容*/protected StringtModifiedBody(String body, ServerWebExchange exchange) {return body;}
}
相关文章:
再谈SpringCloud Gateway源码
再谈SpringCloud Gateway源码 一、整体请求流程二、前置对象准备1、实例化HandlerMapping2、实例化Route3、实例化WebHandler 三、实践业务扩展点1、定义扩展Route对象2、Filter能做什么3、定义扩展Filter对象4、定义父类Filter简化请求参数处理 前言: 之前有阅读过…...
把 CSV 文件摄入到 Elasticsearch 中 - CSVES
在我们之前的很多文章里,我有讲到这个话题。在今天的文章中,我们就提重谈。我们使用一种新的方法来实现。这是一个基于 golang 的开源项目。项目的源码在 https://github.com/githubesson/csves/。由于这个原始的代码并不支持 basic security 及带有安全…...
3.【线性代数】——矩阵乘法和逆矩阵
三 矩阵乘法和逆矩阵 1. 矩阵乘法1.1 常规方法1.2 列向量组合1.3 行向量组合1.4 单行和单列的乘积和1.5 块乘法 2. 逆矩阵2.1 逆矩阵的定义2.2 奇异矩阵2.3 Gauss-Jordan 求逆矩阵2.3.1 求逆矩阵 ⟺ \Longleftrightarrow ⟺解方程组2.3.2 Gauss-Jordan求逆矩阵 1. 矩阵乘法 1.…...
SpringCloud面试题----如何对 Spring Cloud 微服务进行性能优化
架构层面 合理划分微服务 单一职责原则:确保每个微服务只负责单一的业务功能,这样可以降低服务的复杂度,提高可维护性和可扩展性。例如,将用户认证、订单管理、商品管理等不同功能拆分成独立的微服务。避免服务间过度耦合:减少微服务之间的依赖关系,避免因为某个服务的变…...
使用llama.cpp在gpu和cpu上运行deepseek-r1 7b的性能对比
使用deepseek-r1 7b模型的q5km量化版本进行测试, gpu上的token解码速度是cpu的近8倍. 测试环境: ubuntu22.04 x86llama.cpp cpu intel 10750h 4.41 tokens / s model size params backend threads test t/s qwen2 7B Q5_K - Medium 5.07 GiB 7.62 B CPU 6 pp512 …...
BMS项目-面试及答疑整理
1. SOC计算用的什么原理实现的? bms目前计算SOC使用的安时积分+开路电压首先得对电池有一个抽象得概念,把电池比作游泳池,电量比作游泳池里面的水,电流比作流入和流出得水流,那么充电也就是往游泳池里面灌入水流安时积分:对水流进行一个实时监测,比如1S一次监测,那么每…...
【virtiofs】ubuntu24.04+qemu7.0调试virtiofs
文章目录 编译qemu编译buildroot编译linux-6.8.1编译virtiofsd启动脚本qemu调试方法环境: win11 + vmware17 ubuntu24.04 buildroot git clone git://git.busybox.net/buildroot linux-6.8.1 https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.8.1.tar.gz virti…...
【第2章:神经网络基础与实现——2.1 前馈神经网络的结构与工作原理】
老铁们好!今天我们要来一场长达两万字的超详细技术探险,我会像拆解乐高积木一样把前馈神经网络(Feedforward Neural Network)的每个零件摆在台面上,用最接地气的方式让你彻底搞懂这个深度学习基石的工作原理。准备好了吗?我们开始吧! 第一章:神经网络的 “乐高积木” 1…...
ARINC 429详解
ARINC 429 是航空电子系统中广泛应用的一种串行数据总线标准,由航空无线电公司(ARINC)于1977年制定(ARINC 429规范)。它定义了航空电子设备之间数据传输的电气特性、协议格式和通信规则,是民航和军用飞机中…...
C进阶 数据的存储
目录 前言 一,VS的知识储备 二,有趣的scanf()读取 三,数据的存储 引言 四,整数存储 五,小数存储 总结 前言 这里将深入计算机,看计算机是如何进行数据的存储的,怎么在计算机里面筑巢 为…...
第二十二章 P - R 开头的术语
文章目录 第二十二章 P - R 开头的术语程序员模式 (programmer mode)项目 (project)属性 (property)属性排序 (property collation)属性方法 (property method)公有 (public) 以 Q 开头的术语查询 (query)查询接口 (query interface) 以 R 开头的术语范围指示符 (range indicat…...
【C语言】第一期——数据类型变量常量
目录 1 字面量 2 整数类型 2.1 整数类型的取值范围 2.1.1 sizeof 运算符 2.2 GB、MB、KB、B之间的关系 2.3 定义整数类型的变量并打印 2.4 整数类型代码演示 3 浮点类型 3.1 浮点类型的取值范围 3.2 定义浮点类型变量并打印 3.3 保留2位小数点 4 char字符型 4.1…...
【c++】【Linux】【进程】线程终止/崩溃 会导致进程终止/崩溃 吗?
【c】【Linux】【进程】线程终止/崩溃 会导致进程终止/崩溃 吗? 1.线程终止会导致进程终止吗? 在操作系统中,线程是进程的基本执行单元,一个进程可以包含一个或多个线程。 当一个子线程终止时,进程并不会因此自动终…...
宝藏软件系列 篇一:My APK(Android)
文章目录 系列文章官方网站特色功能同类软件 系列文章 官方网站 My APK 官方版本是在 谷歌商店 中上架的。 官方下载地址:Google Play 商店页面。(需要外网) 2025.2最新版本的CSDN本地下载地址(因为是Android App Bundle&…...
springcloud集成gateway
本篇文章只介绍gateway模块的搭建步骤,并无gateway详细介绍 gateway详解请查看:SpringCloudGateway官方文档详解 前置处理 父模块中已指定版本 不知道如何选择版本看这篇: 手把手教你梳理springcloud与springboot与springcloudalibaba的版本…...
pandas(13 Caveats Gotchas和SQL比较)
前面内容:pandas(12 IO工具和稀松数据) 目录 一、Caveats警告 & Gotchas预见 1.1 在Pandas中使用if/Truth语句 1.2 位运算布尔 1.3 isin操作 1.4 重新索引reindex和 loc&iloc 使用注意事项 1.5 loc和iloc 二、Python Pandas 与SQL的比较 2.1 数…...
Android的Activity生命周期知识点总结,详情
一. Activity生命周期 1.1 返回栈知识点 二. Activity状态 2.1 启动状态 2.2 运行状态 2.3 暂停状态 2.4 停止状态 2.5 销毁状态 三. Activity生存期 3.1 回调方法 3.2 生存期 四. 体验Activity的生命周期 五. Activity被回收办法 引言: 掌握Acti…...
基于Python的Flask微博话题舆情分析可视化系统
2024数据 ✅️标价源码 远程部署加 20 ✅️爬虫可用 有六月数据 ✅️修复bug不会突然打不开网页 系统稳定 系统的功能如下: 1.数据的爬取 2.用户的登录注册 3.热词统计,舆情统计 4.文章统计分析 5.发布地址统计 6.评论统计 7.情感分类统计 编程语言:py…...
【Python爬虫(1)】专栏开篇:夯实Python基础
【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取ÿ…...
【油漆面积——线段树,扫描线,不用pushdown的特例,pushup兼有cal的性质】
题目 分析 不用pushdown是因为: 对于modify,操作是互逆过程,因此不会存在向下结算的pushdown过程 对于query,操作始终针对最上层的tr[1],也不需要pushdown 对于pushdown,一则是怕不结算就标记,会…...
fps武器系统6:随机弹道
文章目录 设计随即弹道 思路连射时在一个范围内随机改变枪口旋转即可 实现改变枪口旋转改变旋转就是改变物体朝向即可随机:锥体随机 疑问旋转体与物体朝向(向量)间的关系为什么随即弹道调用两次 设计 随即弹道 思路 连射时在一个范围内随机改变枪口旋转即可 实现…...
深度学习(1)-简单神经网络示例
我们来看一个神经网络的具体实例:使用Python的Keras库来学习手写数字分类。在这个例子中,我们要解决的问题是,将手写数字的灰度图像(28像素28像素)划分到10个类别中(从0到9)。我们将使用MNIST…...
硬件学习笔记--42 电磁兼容试验-6 传导差模电流干扰试验介绍
目录 电磁兼容试验-传导差模电流试验 1.试验目的 2.试验方法 3.判定依据及意义 电磁兼容试验-传导差模电流干扰试验 驻留时间是在规定频率下影响量施加的持续时间。被试设备(EUT)在经受扫频频带的电磁影响量或电磁干扰的情况下,在每个步进…...
Shader示例 6: 卡渲基础 - 描边 + 着色
0 、获取原神模型: 【游戏开发实战】下载原神模型,PMX转FBX,导入到Unity中,卡通渲染,绑定人形动画(附Demo工程)-CSDN博客 《原神》公测视频征集计划 一、描边pass:Outline 1. …...
Cherno C++ P55 宏
这篇文章我们讲一下C当中的宏。其实接触过大型项目的朋友可能都被诡异的宏折磨过。 宏是在预处理当中,通过文本替换的方式来实现一些操作,这样可以不用反复的输入代码,帮助我们实现自动化。至于预处理的过程,其实就是文本编辑&am…...
(20)从strlen到strtok:解码C语言字符函数的“生存指南1”
❤个人主页:折枝寄北的博客 ❤专栏位置:简单入手C语言专栏 目录 前言1. 求字符串长度函数1.1 strlen 2. 长度不受限制的字符串函数2.1 strcpy2.2 strcat2.3 strcmp 3. 长度受限制的字符串函数3.1 strncpy3.2 strncat3.3 strncmp 4. 字符串查找函数4.1 st…...
基于deepseek api和openweather 天气API实现Function Calling技术讲解
以下是一个结合DeepSeek API和OpenWeather API的完整Function Calling示例,包含意图识别、API调用和结果整合: import requests import json import os# 配置API密钥(从环境变量获取) DEEPSEEK_API_KEY os.getenv("DEEPSEE…...
Mongodb数据管理
Mongodb数据管理 1.登录数据库,查看默认的库 [rootdb51~]# mongo> show databases; admin 0.000GB config 0.000GB local 0.000GB> use admin switched to db admin > show tables system.version > admin库:admin 是 MongoDB 的管理…...
从短片到长片:王琦携《Mountain》续作迈向新高度
在王琦(Qi Wang)的带领下,广受关注的短片《Mountain》迎来了成长篇续作《Rite of the Mountain》。这一全新长片不仅是她从短片迈向长篇叙事的重要一步,更是一次大胆的艺术挑战。作为制片人的她,将继续以敏锐的视觉风格和深刻的叙事洞察,拓展《Mountain》所触及的情感深度,并构…...
DeepSeek应用——与PyCharm的配套使用
目录 一、配置方法 二、使用方法 三、注意事项 1、插件市场无continue插件 2、无结果返回,且在本地模型报错 记录自己学习应用DeepSeek的过程,使用的是自己电脑本地部署的私有化蒸馏模型...... (举一反三,这个不单单是可以用…...
