# 网关自定义插件开发指南
适用版本:API Gateway 9.1.0 适用对象:需要在 没有网关源码 的环境下,独立开发、打包并部署自定义网关插件的开发者。
# 目录
# 1. 概述
# 1.1 什么是自定义插件
iPaaS 网关基于 Spring Cloud Gateway 构建。自定义插件本质上就是一个 GatewayFilter(网关过滤器),以独立 JAR 的形式存在。把它放进网关的 lib/ 目录并重启网关后,插件就被加载进网关的类路径,由 Spring 自动扫描注册,随即参与请求/响应的处理链路。
你可以用自定义插件实现诸如:
- 自定义鉴权、签名校验
- 请求/响应日志、审计
- 请求头改写、参数处理
- 响应体脱敏、加解密、内容改写
- 业务限流、灰度路由
- 任何需要在网关层统一处理的逻辑
# 1.2 自定义插件的三要素
一个可被网关识别并使用的自定义插件,至少包含 三个核心部分:
| 组成 | 作用 | 是否必需 |
|---|---|---|
过滤器工厂 (GatewayFilterFactory) | 真正的过滤逻辑入口,决定请求如何被处理 | ✅ 必需 |
策略配置 POJO (XxxPolicyConfig) | 描述"策略参数"的 Java 对象,对应你在管理端配置的那份 JSON | 视插件是否需要策略而定 |
策略缓存类 (XxxPolicyConfigCache) | 把管理端下发的策略数据缓存到内存,供过滤器运行时读取 | 视插件是否需要策略而定 |
💡 如果你的插件逻辑是固定的(不需要在管理端动态配置参数),可以只写过滤器工厂,省略策略配置相关的两个类。本文档为了完整起见,三个都讲。
# 2. 核心概念与运行原理
在动手之前,请先花 3 分钟理解下面的运行原理,它会帮你避开 90% 的坑。
# 2.1 加载与执行全流程
┌──────────────────────────────────────────────────────────────────────┐
│ 网关启动阶段 │
│ │
│ 网关用 PropertiesLauncher (ZIP layout) 启动,启动参数带: │
│ -Dloader.path=<网关根>/lib │
│ │
│ ⇒ Spring Boot 启动类加载器把 lib/ 下【所有 jar】加入 classpath, │
│ 其中包括你放进 lib/ 的自定义插件 jar │
│ │
│ ⇒ Spring Boot 扫描 @SpringBootApplication 主类所在包 │
│ (com.primeton.gateway 及其子包)下的所有 @Component / @Configuration│
│ 并注册为 Bean。你的过滤器工厂、策略缓存类就是这样被自动注册的 │
│ │
│ ⇒ Spring Cloud Gateway 收集所有 GatewayFilterFactory 类型的 Bean, │
│ 供路由 filters 配置引用 │
└──────────────────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────────────────┐
│ 请求处理阶段 │
│ │
│ 每个请求进来 → 命中某条路由 → 该路由配置了你的过滤器 → │
│ 过滤器从策略缓存中读取当前请求对应的策略参数 → 执行业务逻辑 → │
│ chain.filter(exchange) 继续往后传 │
└──────────────────────────────────────────────────────────────────────┘
# 2.2 三个关键点(务必记住)
插件类的包名必须以
com.primeton.gateway开头。 网关主程序GatewayApplication位于com.primeton.gateway包,其@SpringBootApplication默认只扫描该包及其子包。你写的@Component/@Configuration只有落在com.primeton.gateway.**下才会被自动注册。这是网关所有内置插件都遵循的约定(例如com.primeton.gateway.desensitizationGatewayFilter、com.primeton.gateway.custom)。⚠️ 这是最容易踩的坑:插件 jar 已经放进
lib/、类也加载了,但过滤器就是不生效——99% 是包名写成了com.example.xxx,没在扫描范围内。策略缓存类必须加
@Component("策略类型名")注解。 注解里的字符串就是"策略类型名",它必须三处保持完全一致:@Component("这里")getPolicyType()方法的返回值- 过滤器里查询策略时传入的字符串
运行时依赖由网关提供,插件 jar 里不要打包这些依赖。 网关自带 Spring、Spring Cloud Gateway、gateway-core 等,且都在同一个 classloader 里。插件 pom 中这些依赖用
<scope>system</scope>引用网关lib/目录下的 jar(见 3.2 方式 C / 4.2),打包时不会进产物 jar,最终插件 jar 保持"轻量"。这一点见第 5 节,务必遵守。
# 3. 开发环境准备
# 3.1 基础环境
| 工具 | 版本要求 |
|---|---|
| JDK | 1.8(必须,网关运行在 1.8) |
| Maven | 3.5+ |
| IDE | IntelliJ IDEA / Eclipse 任选 |
# 3.2 获取网关依赖(关键,无源码环境)
你开发的插件需要编译期依赖网关提供的几个 jar(最核心的是 gateway-core)。在 没有网关源码 的情况下,推荐用 Maven 的 system 作用域直接引用网关安装包 lib/ 目录下的 jar,完全不需要往本地 .m2 仓库安装任何东西,也不需要公司私服。
每个网关安装包解压后都有 lib/ 目录,里面有全部依赖 jar,例如:
API_GATEWAY/
└── lib/
├── gateway-core-9.1.0.jar ← 提供策略缓存接口、工具类
├── gateway-common-9.1.0.jar ← 提供公共工具
├── spring-cloud-gateway-server-3.1.6.jar
├── spring-boot-2.7.18.jar
└── ...
前置操作(一次性):
- 从网关安装包
API_GATEWAY/lib/目录复制全部 jar 到插件工程下的lib/子目录(工程根目录新建lib/文件夹,把 jar 全部拷进去)。 - 在插件工程的
pom.xml里,把需要的 jar 以<scope>system</scope>+<systemPath>的形式声明依赖。
原理: system 作用域告诉 Maven"这个依赖在文件系统里固定路径下,别去仓库找了"。Maven 编译时直接从 systemPath 指定的路径读 jar,打包时默认也不会把 system 依赖打进产物 jar——所以打出来的天然就是网关需要的 thin jar。
pom 里要声明哪些 jar? 只声明你代码里 import 到的类所属的 jar。下面第 4.2 节给出的示例 pom 只列了 gateway-plugin-custom 示例代码必须的 8 个 jar;后续你扩展功能时,根据新 import 的类属于哪个 jar,按同样格式自行添加即可。
📌 如何判断某个类在哪个 jar 里? 在网关
lib/目录下执行for j in *.jar; do unzip -l "$j" | grep "类名" >/dev/null && echo "$j"; done(git bash 环境),或用 JD-GUI 逐个打开 jar 搜索。常见对应关系见 4.2 节注释。
# 4. 从零开发一个自定义插件(手把手)
下面以"自定义策略插件(CustomPolicy)"为例,源码:my-gateway-plugin.zip,完整演示开发过程。最终效果:过滤器在请求经过时,读取管理端下发的 customParam1 / customParam2 两个参数并打印日志。
# 4.1 新建 Maven 工程
目录结构如下。⚠️ 注意:包名以 com.primeton.gateway 开头,这是网关组件扫描的要求。
my-gateway-plugin/
├── pom.xml
├── lib/ ← 从网关安装包 lib/ 拷来的全部 jar(见 3.2 方式 C)
└── src/main/java/com/primeton/gateway/custom/
├── CustomPolicyGatewayFilterFactory.java ← 过滤器工厂(入口)
└── config/
├── CustomPolicyConfig.java ← 策略配置 POJO
└── CustomPolicyConfigCache.java ← 策略缓存类
# 4.2 编写 pom.xml
<?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>
<groupId>com.example</groupId>
<artifactId>my-gateway-plugin</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- ===== 网关版本与 lib 目录路径(按实际环境修改)===== -->
<gateway.version>9.1.0</gateway.version>
<!-- lib 目录位于插件工程根目录下,内含从网关安装包拷来的全部 jar -->
<gateway.lib>${project.basedir}/lib</gateway.lib>
<!-- ===== 以下版本号务必与你 lib 目录里 jar 的实际文件名一致 ===== -->
<spring.version>5.3.31</spring.version>
<spring-cloud-gateway.version>3.1.6</spring-cloud-gateway.version>
<reactor.version>3.4.22</reactor.version>
<slf4j.version>1.7.36</slf4j.version>
</properties>
<dependencies>
<!--
以下 8 个 jar 是 gateway-plugin-custom 示例代码编译必须的依赖。
采用 system scope 直接引用 lib 目录下的 jar,无需 install 到本地仓库。
打包时 system 依赖不会被打进产物 jar,天然就是 thin jar。
后续扩展功能时,根据你新 import 的类所属 jar,按同样格式自行添加。
-->
<!-- gateway-core:ApiPolicy / PolicyConfigCache / PolicyConfigCacheUtil / JsonUtils -->
<dependency>
<groupId>com.primeton.api</groupId>
<artifactId>gateway-core</artifactId>
<version>${gateway.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/gateway-core-${gateway.version}.jar</systemPath>
</dependency>
<!-- spring-cloud-gateway-server:GatewayFilter / GatewayFilterChain / AbstractGatewayFilterFactory -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server</artifactId>
<version>${spring-cloud-gateway.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/spring-cloud-gateway-server-${spring-cloud-gateway.version}.jar</systemPath>
</dependency>
<!-- spring-core:Ordered -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/spring-core-${spring.version}.jar</systemPath>
</dependency>
<!-- spring-context:@Component -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/spring-context-${spring.version}.jar</systemPath>
</dependency>
<!-- spring-web:ServerWebExchange -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/spring-web-${spring.version}.jar</systemPath>
</dependency>
<!-- spring-beans:Aware -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/spring-beans-${spring.version}.jar</systemPath>
</dependency>
<!-- reactor-core:Mono -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/reactor-core-${reactor.version}.jar</systemPath>
</dependency>
<!-- slf4j-api:Logger / LoggerFactory -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
<scope>system</scope>
<systemPath>${gateway.lib}/slf4j-api-${slf4j.version}.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
📌 几个要点:
system作用域告诉 Maven"依赖在systemPath指定的固定路径下",编译时直接从该路径读 jar,既不需要 install 到本地.m2仓库,也不会被打进产物 jar——所以打出来的天然就是网关需要的 thin jar。gateway.lib指向插件工程下的lib/目录(第 3.2 方式 C 的前置步骤里拷贝的那份)。用${project.basedir}/lib相对路径,工程挪位置不会断。- 版本号:
<properties>里的gateway.version、spring.version等务必与你lib/目录里 jar 的实际文件名一致。不确定时进lib/目录看一眼文件名,例如spring-core-5.3.31.jar对应<spring.version>5.3.31</spring.version>。- 不要打 fat jar。 绝对不要加
spring-boot-maven-plugin做 repackage。system 依赖本就不会进 jar,用默认的maven-compiler-plugin+maven-jar-plugin即可产出纯净的 thin jar。
# 4.3 编写策略配置 POJO —— CustomPolicyConfig.java
这个类就是"你在管理端配置的那份策略 JSON"对应的 Java 对象,字段名要和 JSON 的 key 一致。
package com.primeton.gateway.custom.config;
/**
* 自定义策略配置。
* 对应管理端下发的策略 JSON,例如:
* { "customParam1": "value1", "customParam2": "value2" }
*/
public class CustomPolicyConfig {
private String customParam1;
private String customParam2;
public String getCustomParam1() {
return customParam1;
}
public void setCustomParam1(String customParam1) {
this.customParam1 = customParam1;
}
public String getCustomParam2() {
return customParam2;
}
public void setCustomParam2(String customParam2) {
this.customParam2 = customParam2;
}
}
# 4.4 编写策略缓存类 —— CustomPolicyConfigCache.java
实现网关提供的 PolicyConfigCache 接口,负责把管理端下发的策略存进内存、供过滤器读取。
package com.primeton.gateway.custom.config;
import com.primeton.gateway.core.model.policy.ApiPolicy;
import com.primeton.gateway.core.policy.PolicyConfigCache;
import com.primeton.gateway.util.JsonUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 策略缓存类。
*
* 重点:
* 1. @Component 的值 "CustomPolicy" 就是【策略类型名】,必须全局唯一。
* 2. getPolicyType() 返回值必须与 @Component 的值完全一致。
* 3. 过滤器中查询策略时使用的字符串也必须是同一个值。
*/
@Component("CustomPolicy")
public class CustomPolicyConfigCache implements PolicyConfigCache {
/** 策略缓存容器,key = 策略缓存键,value = 策略配置对象 */
private final Map<String, CustomPolicyConfig> CUSTOM_POLICY_CONFIG_MAP = new ConcurrentHashMap<>();
/**
* 根据缓存键读取策略(过滤器调用)
*/
@Override
public CustomPolicyConfig getPolicyConfig(String policyId) {
return CUSTOM_POLICY_CONFIG_MAP.get(policyId);
}
/**
* 管理端下发策略时,网关会回调此方法,把策略 JSON 反序列化后放进缓存
*/
@Override
public void addPolicyConfig(ApiPolicy apiPolicy) {
// apiPolicy.getPolicyConfig() 就是管理端配置的那段 JSON 字符串
CustomPolicyConfig config = JsonUtils.toObject(apiPolicy.getPolicyConfig(), CustomPolicyConfig.class);
CUSTOM_POLICY_CONFIG_MAP.put(getConfigCacheKey(apiPolicy), config);
}
/**
* 管理端删除策略时回调
*/
@Override
public void deletePolicyConfig(String policyId) {
CUSTOM_POLICY_CONFIG_MAP.remove(policyId);
}
/**
* 策略类型名,必须与 @Component 的值一致
*/
@Override
public String getPolicyType() {
return "CustomPolicy";
}
}
💡
JsonUtils、ApiPolicy、PolicyConfigCache都来自gateway-core,由网关在运行时提供。
# 4.5 编写过滤器工厂 —— CustomPolicyGatewayFilterFactory.java
这是插件的入口类。
package com.primeton.gateway.custom;
import com.primeton.gateway.core.cache.PolicyConfigCacheUtil;
import com.primeton.gateway.custom.config.CustomPolicyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 自定义策略过滤器工厂。
*
* 继承 AbstractGatewayFilterFactory<Config>,泛型参数 Config 是
* "路由配置内联参数"对应的类(本例不需要路由参数,留空即可)。
*
* @Component 让此类被网关注册为 Bean。
*/
@Component
public class CustomPolicyGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomPolicyGatewayFilterFactory.Config> {
private static final Logger logger = LoggerFactory.getLogger(CustomPolicyGatewayFilterFactory.class);
/** 策略类型名,必须与策略缓存类 @Component 的值一致 */
private static final String POLICY_TYPE = "CustomPolicy";
public CustomPolicyGatewayFilterFactory() {
// 必须把 Config 的 Class 传给父类,否则会报 ClassCastException
super(CustomPolicyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
// 这里返回真正的过滤器实例
return new CustomFilter();
}
/**
* 真正的过滤器逻辑写在这里
*/
public static class CustomFilter implements GatewayFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("custom filter execute");
// 从策略缓存中读取当前请求命中的策略
CustomPolicyConfig customPolicy = PolicyConfigCacheUtil.getPolicyConfig(
exchange, POLICY_TYPE, CustomPolicyConfig.class);
if (customPolicy != null) {
String customParam1 = customPolicy.getCustomParam1();
String customParam2 = customPolicy.getCustomParam2();
logger.info("Custom Param1 is {}", customParam1);
logger.info("Custom Param2 is {}", customParam2);
} else {
logger.info("No custom policy matched for this request.");
}
// ⚠️ 务必调用 chain.filter(exchange) 把请求传给下一个过滤器,否则请求会卡住
return chain.filter(exchange);
}
/**
* 过滤器执行顺序,数字越小优先级越高。
* 具体取值见第 7 节说明。
*/
@Override
public int getOrder() {
return 0;
}
}
/**
* 路由内联参数配置类。
* 如果路由里以 filters: [{name: CustomPolicy, args: {...}}] 形式使用,
* args 中的字段会绑定到这里。本插件不需要,留空即可。
*/
public static class Config {
}
}
# 4.6 上线前检查清单 ✅
打包前,请逐条确认:
| 检查项 | 期望 |
|---|---|
| 所有类的包名 | 以 com.primeton.gateway 开头(否则 @Component 不被扫描) |
@Component("X") 的值 | CustomPolicy |
getPolicyType() 返回值 | CustomPolicy |
| 过滤器中查询策略用的字符串 | CustomPolicy |
| 插件是否为 thin jar | 是(依赖为 system 作用域,不会打进产物 jar) |
# 5. 编译打包
在插件工程根目录执行:
mvn clean package
产物位于 target/my-gateway-plugin-1.0.0.jar。
# 5.1 打包注意事项
不要打 fat jar。 绝对不要使用
spring-boot-maven-plugin把依赖一起 repackage 进来。本文 4.2 的 pom 用system作用域引用lib/目录下的 jar,这些依赖不会进产物 jar,用默认的maven-compiler-plugin即可产出纯净的 thin jar,这正是网关期望的格式。fat jar(即用 spring-boot repackage 后的 jar)里的类位于
BOOT-INF/classes/,Spring Boot 的loader.path加载这种 jar 时会读不到你的类,导致插件完全不生效。验证 jar 内容(强烈建议):
# 应该只看到你自己写的几个 class,不应出现 spring / reactor / gateway-core 等第三方包 jar tf target/my-gateway-plugin-1.0.0.jar | head -20预期输出大致:
META-INF/ META-INF/MANIFEST.MF com/primeton/gateway/custom/ com/primeton/gateway/custom/CustomPolicyGatewayFilterFactory.class com/primeton/gateway/custom/CustomPolicyGatewayFilterFactory$Config.class com/primeton/gateway/custom/CustomPolicyGatewayFilterFactory$CustomFilter.class com/primeton/gateway/custom/config/CustomPolicyConfig.class com/primeton/gateway/custom/config/CustomPolicyConfigCache.class如果看到
org/springframework/...或BOOT-INF/,说明打包配置错了,回到 4.2 检查。
# 6. 部署到网关生效
# 6.1 网关目录结构
解压网关安装包后,典型目录结构如下:
API_GATEWAY/
├── bin/ ← 启动/停止脚本(startup.sh / startup.bat)
├── config/ ← 配置文件(application.properties 等)
├── lib/ ← 网关自身依赖 jar;自定义插件 jar 也放在这里
├── license/ ← 许可证
├── keystore/ ← 证书
├── logs/ ← 日志输出
└── gateway-boot-9.1.0.jar ← 网关主程序(ZIP layout 可执行 jar)
启动脚本通过 -Dloader.path=<网关根>/lib 让 Spring Boot 在启动时加载 lib/ 下的全部 jar。因此自定义插件 jar 放进 lib/ 目录、重启网关即可生效。
# 6.2 放置插件 jar
# 把打包好的插件 jar 放进网关 lib 目录
cp my-gateway-plugin-1.0.0.jar API_GATEWAY/lib/
放置后的结构:
API_GATEWAY/
├── bin/
├── lib/
│ ├── gateway-core-9.1.0.jar
│ ├── ...(网关原有依赖)
│ └── my-gateway-plugin-1.0.0.jar ← 你的自定义插件
└── ...
📌 jar 的文件名随意,Spring Boot loader 会加载
lib/下所有*.jar,与文件名无关。建议用清晰的插件名-版本.jar命名以便管理。
# 6.3 重启网关生效
cd API_GATEWAY/bin
./shutdown.sh # Windows 用 shutdown.bat
./startup.sh # Windows 用 startup.bat
新增/替换插件 jar 后,必须重启网关才能生效(
lib/目录在 JVM 启动那一刻固定下来,运行中新增 jar 不会被加载)。
# 6.4 验证插件已生效
方式一:看启动日志。 网关启动过程中,Spring 会扫描注册你的 Bean。可以在 logs/ 下的应用日志中搜索插件类名,应能看到 Bean 初始化相关的日志;过滤器里 logger.info("create custom filter") / custom filter execute 也会在请求经过时打印。
方式二:发请求实测。 给某条路由的 filters 加上你的过滤器名(见 6.5),发起一次请求,观察日志里是否出现 custom filter execute,以及策略参数是否被正确读取打印。
# 6.5 让路由使用你的插件
插件被加载后,它就像内置插件一样可用。在管理端(Governor)配置路由的 filters,加上你的过滤器名即可:
filters:
- CustomPolicy
过滤器名取的是 GatewayFilterFactory 类名去掉 GatewayFilterFactory 后缀的部分(即 CustomPolicy)。
# 6.6 卸载插件
停止网关 → 从 lib/ 移除对应 jar → 启动网关。同时记得在路由配置中移除对该过滤器的引用,避免路由引用了不存在的过滤器。
# 7. 进阶:让插件真正处理业务
上面的示例只是在 chain.filter(exchange) 之前 打了日志。真正实用的插件通常需要做更多事情,下面给出几种常见写法。
# 7.1 读取请求信息
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求方法、URI
String method = exchange.getRequest().getMethodValue();
URI uri = exchange.getRequest().getURI();
// 请求头
String clientId = exchange.getRequest().getHeaders().getFirst("ClientId");
String operationCode = exchange.getRequest().getHeaders().getFirst("OperationCode");
// 业务约定:网关通过这两个请求头识别调用方和接口
logger.info("ClientId={}, OperationCode={}", clientId, operationCode);
return chain.filter(exchange);
}
# 7.2 前置处理:修改请求头后继续放行
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 给下游加上一个自定义请求头
ServerHttpRequest request = exchange.getRequest().mutate()
.header("X-My-Plugin", "custom-value")
.build();
return chain.filter(exchange.mutate().request(request).build());
}
# 7.3 前置拦截:直接拒绝请求
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (!checkOk(exchange)) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 不调用 chain.filter,直接返回,请求到此终止
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
# 7.4 后置处理:修改响应体(例如脱敏、加解密)
响应体修改需要使用 ServerHttpResponseDecorator 装饰响应,并 return chain.filter(exchange.mutate().response(decorated).build())。这是最复杂的场景,可参考内置"脱敏插件"(DesensitizationGatewayFilterFactory)的写法,核心模式如下:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (Objects.equals(getStatusCode(), HttpStatus.OK) && body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// 合并所有 buffer,读取原始响应体
// ... 解析、改写、重新包装 ...
return bufferFactory().wrap(newBody.getBytes(StandardCharsets.UTF_8));
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
# 8. 策略配置数据说明
如果你的插件需要"动态可配置的参数"(比如本例的 customParam1/customParam2),需要配合管理端(Governor)下发策略数据:
- 管理端创建一个策略类型为
CustomPolicy(与代码中的策略类型名一致)的策略,填入策略配置 JSON,例如:{ "customParam1": "hello", "customParam2": "world" } - 将该策略绑定到某条路由 / 接口 / 客户端。
- 网关接收到策略后,会回调你写的
CustomPolicyConfigCache#addPolicyConfig,把 JSON 反序列化为CustomPolicyConfig存入缓存。 - 请求到达时,
PolicyConfigCacheUtil.getPolicyConfig(exchange, "CustomPolicy", ...)会根据当前请求上下文(路由、OperationCode、ClientId 等)匹配到对应策略并返回。
💡 匹配优先级:ClientId+OperationCode > OperationCode > RouteId > SystemCode。具体由
PolicyConfigCacheUtil内部决定,插件无需关心。
如果你不需要动态策略,可以删除策略配置相关的两个类,过滤器里直接写死逻辑即可。
# 9. 常见问题与排错
# Q1:插件 jar 已放进 lib/,但过滤器不生效
最常见原因:包名不在扫描范围。
- 网关只扫描
com.primeton.gateway.**包。请确认你的所有类(特别是带@Component的过滤器工厂和策略缓存类)都在com.primeton.gateway或其子包下。 - 确认路由的
filters已加上过滤器名(去掉GatewayFilterFactory后缀的部分)。 - 确认已重启网关(运行中放 jar 不生效)。
# Q2:启动报 ClassCastException / LinkageError
原因:把 spring / gateway 等依赖打进了插件 jar,与网关自身的类冲突。
解决:pom 中运行期依赖用 system 作用域引用 lib/ 目录下的 jar(见 4.2),重新打包;确认产物是 thin jar(不含 BOOT-INF/、不含第三方包)。
# Q3:插件用了 spring-boot-maven-plugin 打包,启动后完全不生效
原因:打成了 fat jar,类在 BOOT-INF/classes/,Spring Boot loader 读不到。
解决:移除 spring-boot-maven-plugin,用普通 maven-compiler-plugin + 默认 jar 打包即可(见 4.2 pom)。
# Q4:策略读不到(getPolicyConfig 返回 null)
原因:策略类型名不一致,或策略未下发。 排查:
- 检查
@Component("X")、getPolicyType()返回值、过滤器中查询字符串三者是否完全相同(区分大小写)。 - 确认管理端已创建并下发策略。
- 确认
CustomPolicyConfigCache被注册为 Bean(包名要在com.primeton.gateway下)。
# Q5:插件代码里 @Autowired 注入的 Bean 是 null
原因:过滤器实例是 apply() 方法里 new 出来的普通对象,不经过 Spring 容器,无法用 @Autowired。
解决:
- 改用
PolicyConfigCacheUtil/ApplicationBeanUtils.getBean(...)等工具主动获取; - 或在过滤器工厂(它是 Bean)中注入所需依赖后,通过构造参数传给内部过滤器。
# Q6:Windows 下替换 jar 报"文件被占用"
原因:JVM 运行期间持有 jar 文件句柄。 解决:先停止网关,再替换 jar。
# 10. 完整代码清单
下面三个文件即本指南配套示例的全部源码(与 gateway-plugin-custom 模块示例对应,已做命名规范与注释增强)。
# config/CustomPolicyConfig.java
package com.primeton.gateway.custom.config;
public class CustomPolicyConfig {
private String customParam1;
private String customParam2;
public String getCustomParam1() { return customParam1; }
public void setCustomParam1(String customParam1) { this.customParam1 = customParam1; }
public String getCustomParam2() { return customParam2; }
public void setCustomParam2(String customParam2) { this.customParam2 = customParam2; }
}
# config/CustomPolicyConfigCache.java
package com.primeton.gateway.custom.config;
import com.primeton.gateway.core.model.policy.ApiPolicy;
import com.primeton.gateway.core.policy.PolicyConfigCache;
import com.primeton.gateway.util.JsonUtils;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component("CustomPolicy")
public class CustomPolicyConfigCache implements PolicyConfigCache {
private final Map<String, CustomPolicyConfig> CUSTOM_POLICY_CONFIG_MAP = new ConcurrentHashMap<>();
@Override
public CustomPolicyConfig getPolicyConfig(String policyId) {
return CUSTOM_POLICY_CONFIG_MAP.get(policyId);
}
@Override
public void addPolicyConfig(ApiPolicy apiPolicy) {
CustomPolicyConfig config = JsonUtils.toObject(apiPolicy.getPolicyConfig(), CustomPolicyConfig.class);
CUSTOM_POLICY_CONFIG_MAP.put(getConfigCacheKey(apiPolicy), config);
}
@Override
public void deletePolicyConfig(String policyId) {
CUSTOM_POLICY_CONFIG_MAP.remove(policyId);
}
@Override
public String getPolicyType() {
return "CustomPolicy";
}
}
# CustomPolicyGatewayFilterFactory.java
package com.primeton.gateway.custom;
import com.primeton.gateway.core.cache.PolicyConfigCacheUtil;
import com.primeton.gateway.custom.config.CustomPolicyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomPolicyGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomPolicyGatewayFilterFactory.Config> {
private static final Logger logger = LoggerFactory.getLogger(CustomPolicyGatewayFilterFactory.class);
private static final String POLICY_TYPE = "CustomPolicy";
public CustomPolicyGatewayFilterFactory() {
super(CustomPolicyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new CustomFilter();
}
public static class CustomFilter implements GatewayFilter, Ordered {
private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
logger.info("custom filter execute");
CustomPolicyConfig customPolicy = PolicyConfigCacheUtil.getPolicyConfig(
exchange, POLICY_TYPE, CustomPolicyConfig.class);
if (customPolicy != null) {
logger.info("Custom Param1 is {}", customPolicy.getCustomParam1());
logger.info("Custom Param2 is {}", customPolicy.getCustomParam2());
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
public static class Config {
}
}
至此,你已经具备在无网关源码环境下,独立开发、打包并部署一个网关自定义插件所需的全部知识。祝开发顺利!