Spring Cloud - 手写 Gateway 源码,实现自定义局部 FilterFactory
目录
一、FilterFactory 分析
1.1、前置知识
1.2、分析源码
1.2.1、整体分析
1.2.2、源码分析
1.3、手写源码
1.3.1、基础框架
1.3.2、实现自定义局部过滤器
1.3.3、加参数的自定义局部过滤器器
一、FilterFactory 分析
1.1、前置知识
前面的学习我们知道,GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,同时,springcloud 也提供了一些内置的 filter.
比如:StripPrefix,表示给请求的 url 中去表指定的 n 个前缀路由,例如 - StripPrefix=2 那么如果你原本的请求是路由是 /user/list/get ,那么经过 StripPrefix 处理后,就会变成 /get.
如果我们需要自己去实现一个像这样的局部过滤器,该怎么实现呢?
1.2、分析源码
1.2.1、整体分析
例如 StripPrefix,他继承了 AbstractGatewayFilterFactory 这个抽象类.
这里暗含了一层意思:在 application.yml 配置文件中,可以在 filters 配置里写上这个类的前缀 StripPrefix,就表示这个类(后面的 GatewayFilterFactory 是固定写法,就表示他是一个网关过滤器).
进一步的,如果我们要自定义一个局部过滤器,例如身份认证 Token 过滤器,我们就创建一个类命名为:Token + GatewayFilter,然后继承 AbstractGatewayFilterFactory 抽象类,就表示他是一个局部过滤器.
1.2.2、源码分析
源码如下:
public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {public static final String PARTS_KEY = "parts";public StripPrefixGatewayFilterFactory() {super(Config.class);}public List<String> shortcutFieldOrder() {return Arrays.asList("parts");}public GatewayFilter apply(final Config config) {return new GatewayFilter() {public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());String path = request.getURI().getRawPath();String[] originalParts = StringUtils.tokenizeToStringArray(path, "/");StringBuilder newPath = new StringBuilder("/");for(int i = 0; i < originalParts.length; ++i) {if (i >= config.getParts()) {if (newPath.length() > 1) {newPath.append('/');}newPath.append(originalParts[i]);}}if (newPath.length() > 1 && path.endsWith("/")) {newPath.append('/');}ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build();exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());return chain.filter(exchange.mutate().request(newRequest).build());}public String toString() {return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();}};}public static class Config {private int parts = 1;public Config() {}public int getParts() {return this.parts;}public void setParts(int parts) {this.parts = parts;}}
}
- extends AbstractGatewayFilterFactory<Config> :继承 AbstractGatewayFilterFactory 表示他是一个局部过滤器. 传入一个泛型 Config(是一个静态内部类),是因为在配置 filters 的时候,可能需要给参数指定具体的值,例如 - StripPrefix=2,而 Config 就是来处理这里的 2 这个值的.
- public static final String PARTS_KEY = "parts": 这里就是定义一个常量,后面会用上.
- StripPrefixGatewayFilterFactory() :构造方法,需要给父类 AbstractGatewayFilterFactory 传递 Config 参数(前面分析过了),将来在 apply 方法中会用上.
- Config:是一个静态内部类,描述了配置 filters 时,具体要给参数指定的值,并提供了 get 和 set 方法. 这个类就需要传递给父类 AbstractGatewayFilterFactory,最后回传给 apply 方法,在 apply 方法中使用.
如果不想给参数指定值,就可以不写 Config 中的内容.
- shortcutFieldOrder():这个方法是用来指定 filters 配置中参数值的顺序. 也就是说,如果 Config 个中如果有多个参数,那么你在配置 filters 时,指定参数的多个值,顺序是怎样的?他就是用来指定顺序的.
- apply(Config config):局部过滤器的核心类,用来描述过滤规则的. 这里的参数 Config 就是刚刚讲到的 静态内部类,先传递给父类,然后再回传给了 apply 方法,之后我们就可以直接在 apply 方法中去使用 Config 类中的参数.
1.3、手写源码
1.3.1、基础框架
按照上述分析,不难写出大概模样,例如我们可以模仿源码,创建包 filter.factory ,然后在这个包下定义一个 Token 局部过滤器如下:
@Component // 表示在工厂中创建对象(不能少!)
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {public TokenGatewayFilterFactory() {super(Config.class);}/*** 核心方法: 处理过滤* @param config* @return*/@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 处理过滤逻辑......return chain.filter(exchange);}};}@Overridepublic List<String> shortcutFieldOrder() {return super.shortcutFieldOrder();}public static class Config {}}
那么我们就可以在配置文件中,添加这个自定义的局部过滤器
1.3.2、实现自定义局部过滤器
例如,自定义一个 Token 局部过滤器,那么就可以创建一个类 filter.factory.TokenGatewayFilterFactory
在 apply 中的过滤逻辑就是,判断前端是否传入 token,如果没有就抛异常,如果有就去 redis 上看看是否存在这个 token,如果存在就放行,不存在就抛异常.
@Slf4j
@Component // 表示在工厂中创建对象
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {@Autowiredprivate StringRedisTemplate redisTemplate;public TokenGatewayFilterFactory() {super(Config.class);}/*** 核心方法: 处理过滤* @param config* @return*/@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取 token 信息//由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 ListList<String> tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);if(tokens == null) {throw new RuntimeException("没有 token 令牌!");}String tokenValue = tokens.get(0);log.info("token: {}", tokenValue);//2.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {throw new RuntimeException("token 令牌不合法!");}return chain.filter(exchange);}};}@Overridepublic List<String> shortcutFieldOrder() {return super.shortcutFieldOrder();}public static class Config {}}
a)例如 redis 存储的数据为
b)执行结果如下:
c)如果没有 token 数据,响应如下:
d)如果有 token,但是 token 值错误,响应如下:
1.3.3、加参数的自定义局部过滤器器
如果在配置 filters 的时候,要指定一些参数,例如 isRequire(boolean类型,表示是否传),name(String 类型).
那么就可以在 Config 静态内部类中描述,然后在 shortcutFieldOrder() 方法中指定顺序,最后就可以在 apply 中拿到对应的参数,如下:
@Slf4j
@Component // 表示在工厂中创建对象
public class TokenGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenGatewayFilterFactory.Config> {@Autowiredprivate StringRedisTemplate redisTemplate;public TokenGatewayFilterFactory() {super(Config.class);}/*** 核心方法: 处理过滤* @param config* @return*/@Overridepublic GatewayFilter apply(Config config) {return new GatewayFilter() {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("config isRequire: {}", config.isRequire());log.info("config name: {}", config.getName());//1.拿到 Config 中自定义的参数 isRequire,判断是否要进行过滤if(config.isRequire()) {//2.获取 token 信息//由于 header 中 key 可以重复(包括 parma 也是如此),因此获取到的是一个 ListList<String> tokens = exchange.getRequest().getHeaders().get(RedisPrefix.TOKEN_KEY);if(tokens == null) {throw new RuntimeException("没有 token 令牌!");}String tokenValue = tokens.get(0);log.info("token: {}", tokenValue);//3.比较 redis 上的 token 数据是否一致(redis 上存储的数据格式为: token前缀 + value)if(!redisTemplate.hasKey(RedisPrefix.TOKEN_KEY + tokenValue)) {throw new RuntimeException("token 令牌不合法!");}}return chain.filter(exchange);}};}/*** 指定参数值填写的顺序* @return*/@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("require", "name");}/*** 提供 filter 配置中的参数值*/public static class Config {private boolean require;private String name;public boolean isRequire() {return require;}public void setRequire(boolean require) {this.require = require;}public String getName() {return name;}public void setName(String name) {this.name = name;}}}
Ps:这里的不要命名为 isRequire ,会冲突!
在配置文件中配置 filters:
执行结果如下:
相关文章:

Spring Cloud - 手写 Gateway 源码,实现自定义局部 FilterFactory
目录 一、FilterFactory 分析 1.1、前置知识 1.2、分析源码 1.2.1、整体分析 1.2.2、源码分析 1.3、手写源码 1.3.1、基础框架 1.3.2、实现自定义局部过滤器 1.3.3、加参数的自定义局部过滤器器 一、FilterFactory 分析 1.1、前置知识 前面的学习我们知道,…...

14、Python继承和多态:基础继承、方法重写、多态的基本概念
文章目录 基础继承方法重写多态在编程语言中,特别是在面向对象编程(OOP)中,继承和多态是两个核心概念,它们提供了代码重用和接口设计的强大工具。Python 作为一种支持 OOP 的语言,也具备这些特性。 基础继承 继承是面向对象编程的一个基本概念,它允许我们定义一个类(…...
聊聊logback的StatusManager
序 本文主要研究一下logback的StatusManager StatusManager ch/qos/logback/core/status/StatusManager.java public interface StatusManager {/*** Add a new status message.* * param status*/void add(Status status);/*** Obtain a copy of the status list maintain…...

[PyTorch][chapter 61][强化学习-免模型学习1]
前言: 在现实的学习任务中,环境 其中的转移概率P,奖赏函数R 是未知的,或者状态X也是未知的 称为免模型学习(model-free learning) 目录: 1: 蒙特卡洛强化学习 2:同策略-蒙特卡洛强化学习 3&am…...
网络运维Day04-补充
文章目录 周期性计划任务周期性计划任务使用案例一案例二 周期性计划任务 在固定时间可以完成相同的任务,被称之为周期性计划任务由crond服务提供需要将定时任务,写到一个文件书写格式如下 分 时 日 月 周 任务(绝对路径)分:0-59时ÿ…...

前端埋点方式
前言: 想要了解用户在系统中所做的操作,从而得出用户在本系统中最常用的模块、在系统中停留的时间。对于了解用户的行为、分析用户的需求有很大的帮助,想实现这种需求可以通过前端埋点的方式。 埋点方式: 1.什么是埋点?…...
iOS导航栏返回按钮
导航栏返回按钮隐藏: override func pushViewController(_ viewController: UIViewController, animated: Bool) {if let vc self.viewControllers.last {let backItem UIBarButtonItem()backItem.title ""vc.navigationItem.backBarButtonItem backI…...

2023中国视频云市场报告:腾讯云音视频解决方案份额连续六次蝉联榜首,加速全球化布局
近日,国际数据公司(IDC)发布了《中国视频云市场跟踪(2023上半年)》报告,腾讯云音视频的解决方案份额连续六次蝉联榜首,并在视频生产创作与媒资管理市场份额中排名第一。同时,在实时音…...
jpa Repository的常用写法总结
一、前言 之前项目在xml中写sql,感觉标签有很多,比较灵活; 最近在写新项目,使用了jpa,只能在java中写sql了,感觉不太灵活,但是也得凑付用。 以下总结下常用入参出参写法。 二、Repository代…...

笔记本电脑 禁用/启用 自带键盘
现在无论办公还是生活 很多人都会选择笔记本电脑 但很多人喜欢机械键盘 或者 用一些外接键盘 但是很多时候我们想操作 会碰到笔记本原来的键盘导致错误操作 那么 我们就需要将笔记本原来的键盘禁用掉 我们先以管理员身份运行命令窗口 然后 有两个命令 禁用默认键盘 sc conf…...

基于 golang 从零到一实现时间轮算法 (三)
引言 本文参考小徐先生的相关博客整理,项目地址为: https://github.com/xiaoxuxiansheng/timewheel/blob/main/redis_time_wheel.go。主要是完善流程以及记录个人学习笔记。 分布式版实现 本章我们讨论一下,如何基于 redis 实现分布式版本的…...

k8s 1.28安装
容器运行时,containerd 按照官方的指导,需要安装runc和cni插件,提示的安装方式,有三种: 二进制安装包源码apt-get 或 dnf安装 我们这里选用第三种,找到docker官方提供的安装方式 ubuntu-containerd # A…...

安装anaconda时控制台conda-version报错
今天根据站内的一篇博客教程博客在此安装anaconda时,检查conda版本时报错如下: >>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<< Traceback (most recent call last): File “D:\An…...

链表(1)
目录 单链表 主函数test.c test1 test2 test3 test4 头文件&函数声明SList.h 函数实现SList.c 打印SLPrint 创建节点CreateNode 尾插SLPushBack 头插SLPushFront 头删SLPopBck 尾删SLPopFront 易错点 本篇开始链表学习。今天主要是单链表&OJ题目。 单链…...

智慧农业:农林牧数据可视化监控平台
数字农业是一种现代农业方式,它将信息作为农业生产的重要元素,并利用现代信息技术进行农业生产过程的实时可视化、数字化设计和信息化管理。能将信息技术与农业生产的各个环节有机融合,对于改造传统农业和改变农业生产方式具有重要意义。 图扑…...

知识注入以对抗大型语言模型(LLM)的幻觉11.6
知识注入以对抗大型语言模型(LLM)的幻觉 摘要1 引言2 问题设置和实验2.1 幻觉2.2 生成响应质量 3 结果和讨论3.1 幻觉3.2 生成响应质量 4 结论和未来工作 摘要 大型语言模型(LLM)内容生成的一个缺点是产生幻觉,即在输…...
机器人物理交互场景及应用的实际意义
机器人物理交互场景是指机器人与物理世界或人类进行实际的物理互动和交互的情境。这些场景涉及机器人在不同环境和应用中使用其物理能力,以执行任务、提供服务或与人类互动。 医疗协助: 外科手术助手:机器人可以用于外科手术,提供…...

Kubernetes Dashboard 用户名密码方式登录
Author:rab 前言 为了 K8s 集群安全,默认情况下 Dashboard 以 Token 的形式登录的,那如果我们想以用户名/密码的方式登录该怎么操作呢?其实只需要我们创建用户并进行 ClusterRoleBinding 绑定即可,接下来是具体的操作…...
Redisson中的对象
Redisson - 是一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, …...

GNU ld链接器 lang_process()(二)
一、ldemul_create_output_section_statements() 位于lang_process()中11行 。 该函数用于创建与目标有关的输出段的语句。这些语句将用于描述输出段的属性和分配。 void ldemul_create_output_section_statements (void) {if (ld_emulation->create_output_section_sta…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...

门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...
软件工程教学评价
王海林老师您好。 您的《软件工程》课程成功地将宏观的理论与具体的实践相结合。上半学期的理论教学中,您通过丰富的实例,将“高内聚低耦合”、SOLID原则等抽象概念解释得十分透彻,让这些理论不再是停留在纸面的名词,而是可以指导…...
湖北理元理律师事务所:债务清偿方案中的法律技术革新
文/金融法律研究组 当前债务服务市场存在结构性矛盾:债权人追求快速回款,债务人需要喘息空间。湖北理元理律师事务所通过创新法律技术,在《企业破产法》《民法典》框架下构建梯度清偿模型,实现多方利益平衡。 一、个人债务优化的…...