news 2026/9/25 3:02:24

Apache ShenYu Wasm 数据同步插件实战:将 Rust 编写的 PluginDataHandler 编译为 wasm 并与 Java 侧集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apache ShenYu Wasm 数据同步插件实战:将 Rust 编写的 PluginDataHandler 编译为 wasm 并与 Java 侧集成
  • 后端
  • API网关
  • 微服务

【免费下载链接】shenyu

Apache ShenYu is a Java native API Gateway for service proxy, protocol conversion and API governance.

项目地址:https://gitcode.com/gh_mirrors/sh/shenyu
点击查看免费下载

本文以 ShenYu 仓库中shenyu-plugin-wasm-base模块的 Rust 测试样例rust-plugin-data-handler为主体,完整讲解该样例的 wasm 文件构建流程(安装工具链、cargo build --target wasm32-wasi、产物命名与重命名规则),并结合仓库源码剖析 Java 侧如何通过 Wasmtime 加载 wasm、通过shenyu导入模块实现 Rust 与 Java 的双向数据交换。读完本文,你可以独立完成一次完整的 “Rust 编写数据处理器 → 编译为 wasm → 以类名规则重命名 → 作为 classpath 资源被 Java 测试加载运行” 的闭环操作。

一、这个 README 在整个 wasm 插件体系中处于什么位置

ShenYu 对插件的扩展提供了 WebAssembly 支持,核心代码集中在两个模块:

  • shenyu-plugin-wasm-api:提供 WasmLoader 加载器,负责定位.wasm文件、实例化模块、把 Java 函数以导入(import)形式暴露给 wasm;
  • shenyu-plugin-wasm-base:提供三类AbstractWasm*Handler抽象基类,其中 AbstractWasmPluginDataHandler 对应插件、选择器、规则的数据同步回调(handlerPlugin/removePlugin/handlerSelector/removeSelector/handlerRule/removeRule)。

本 README 所在的 rust-plugin-data-handler 目录,就是为验证AbstractWasmPluginDataHandler而编写的 Rust 侧配套工程。目录内的文件分工如下:

文件作用
src/lib.rsRust 插件逻辑,导出 6 个数据同步回调函数,并导入 Java 侧的get_args/put_result
Cargo.toml声明crate-type = ["cdylib"],使构建产物为可被 Wasmtime 加载的共享库形态 wasm
AbstractWasmPluginDataHandlerTest.javaJava 测试类,内部类TestWasmPluginDataHandler继承抽象基类并定义回调到 Java 的函数
src/test/resources 下的 .wasm 文件编译并改名后的 wasm 二进制,作为 classpath 资源供测试加载

二、构建前置:安装 Rust 工具链

README 给出的前两步是安装 rustup 与 Rust。补充一点实操细节:构建命令指定了--target wasm32-wasi,而该目标并不随 Rust 默认工具链安装,需要通过 rustup 显式添加:

rustup target add wasm32-wasi

这是执行下文构建命令的前提条件(README 未展开此步,属于构建wasm32-wasi目标的通用要求)。

三、生成 wasm 文件:构建命令与产物路径

README 中的核心构建命令为:

cd {shenyu}/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-plugin-data-handler cargo build --target wasm32-wasi --release

其中{shenyu}为 ShenYu 仓库根目录的实际路径。构建完成后,README 指出产物位于:

{shenyu}/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-plugin-data-handler/target/wasm32-wasi/release/rust_wasm_data_handler_plugin.wasm

这里有一个值得注意的源码级事实:当前 Cargo.toml 中包名为rust-plugin-data-handler,且crate-type为cdylib。按照 Cargo 对 cdylib 的命名约定,实际产物文件名应为librust_plugin_data_handler.wasm(带lib前缀、连字符转下划线)。这说明 README 中记录的rust_wasm_data_handler_plugin.wasm对应的是较早版本包名,执行改名步骤时应以target/wasm32-wasi/release/目录下实际生成的.wasm文件为准,不要机械照抄旧文件名。

四、重命名规则:为什么 wasm 文件名必须叫...Test$TestWasmPluginDataHandler.wasm

README 第 4 步要求把产物重命名为:

org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandlerTest$TestWasmPluginDataHandler.wasm

这个看似奇怪的命名(含$、长得像一个 Java 类名)并非随意规定,而是由加载器的资源定位逻辑决定的。查看 WasmLoader 构造函数:

this.wasmName = clazz.getName() + ".wasm"; // locate `.wasm` lib. URL resource = clazz.getClassLoader().getResource(wasmName); if (Objects.isNull(resource)) { throw new ShenyuWasmInitException("Can't find wasm file: " + wasmName); }

要点有三:

  1. 文件名 = 类的全限定名 +.wasm。AbstractWasmPluginDataHandler继承WasmLoader并走默认构造(this(null, null)),此时clazz即当前实例的运行时类——测试中的内部类TestWasmPluginDataHandler。Java 内部类的Class.getName()用$连接外层类名,所以得到org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandlerTest$TestWasmPluginDataHandler,这正是 README 要求的目标文件名;
  2. wasm 文件必须位于 classpath 根路径下。getClassLoader().getResource(wasmName)是按类路径资源查找的,因此改名后的.wasm要放到 src/test/resources 目录下,随测试 classpath 一起生效。仓库中已存在该文件,即为一次构建改名后的入库产物;
  3. 找不到即快速失败,抛出ShenyuWasmInitException。如果跳过 README 第 4 步的重命名(或放错目录),测试会在构造TestWasmPluginDataHandler时直接报 "Can't find wasm file" 异常,这也是该步骤不可省略的原因。

同一约定也适用于模块内另外三个 Rust 样例:rust-discovery-handler、rust-meta-data-handler、rust-shenyu-wasm-plugin的 README 均采用相同流程,且src/test/resources下对应存在四个.wasm资源文件,命名均与各自测试的内部类名一一对应。

五、源码纵深:Rust 与 Java 之间的双向调用协议

理解“为什么要构建这个 wasm”需要看懂两侧代码如何配合。

5.1 Rust 侧:导入 Java 函数 + 导出 6 个回调

lib.rs 开头声明了对外部模块shenyu的导入:

#[link(wasm_import_module = "shenyu")] extern "C" { fn get_args(arg_id: i64, addr: i64, len: i32) -> i32; fn put_result(arg_id: i64, addr: i64, len: i32) -> i32; }

get_args用于从 Java 侧线性内存读取参数(如序列化后的 PluginData 内容),put_result用于把 Rust 处理结果写回 Java 侧内存。随后通过#[no_mangle]导出 6 个与AbstractWasmPluginDataHandler常量完全对应的回调:handlerPlugin、removePlugin、handlerSelector、removeSelector、handlerRule、removeRule(lib.rs L38-L72),每个回调内部都会调用impls(arg_id)演示一次完整的数据往返。

5.2 Java 侧:shenyu导入模块由 WasmLoader 统一命名

shenyu这个导入模块名在 Java 侧的常量是 WasmLoader 中的IMPORT_WASM_MODULE_NAME = "shenyu"。测试类通过覆写initWasmCallJavaFunc把get_args/put_result两个 Java 闭包注册进wasmCallJavaFuncMap,由Linker定义到shenyu模块下。测试中的实现(AbstractWasmPluginDataHandlerTest.java L146-L171):

  • get_args:把字符串"hello from java " + argId逐字节写入 wasm 线性内存(通过super.getBuffer()拿到ByteBuffer),断言收到的argId为0;
  • put_result:按地址从线性内存读回字节,断言内容为 Rust 写回的"rust result"。

两侧断言互为镜像:Java 写"hello from java 0",Rust 断言读取成功;Rust 写"rust result",Java 断言读取成功——构成一次端到端的内存交换验证。

5.3 参数如何跨越 wasm 边界:argId间接寻址模式

Wasm 与 Java 之间无法像 JNI 那样直接传对象引用,AbstractWasmPluginDataHandler 的 callWASI 因此采用了间接寻址:

final Long argumentId = getPluginArgumentId(pluginData); PLUGIN_ARGUMENTS.put(argumentId, pluginData); WasmFunctions.consumer(super.getStore(), execute.func(), WasmValType.I64) .accept(argumentId); PLUGIN_ARGUMENTS.remove(argumentId);

Java 先把PluginData按Long型 ID 存入静态ConcurrentHashMap,再向 wasm 函数只传一个 I64 参数;wasm 侧拿到 ID 后回调get_args(arg_id, addr, len),Java 闭包凭 ID 从 Map 中取回真实对象并序列化进内存。测试中getPluginArgumentId保持基类默认值0L,这正解释了测试里对"hello from java 0"的断言。

六、完整操作清单与验证方式

综合 README 与源码,一次完整的操作顺序如下:

# 1. 安装 rustup 与 Rust(略,按官方方式安装) # 2. 确保 wasm32-wasi 目标已安装 rustup target add wasm32-wasi # 3. 构建({shenyu} 替换为仓库根目录实际路径) cd {shenyu}/shenyu-plugin/shenyu-plugin-wasm-base/src/test/rust-plugin-data-handler cargo build --target wasm32-wasi --release # 4. 将 target/wasm32-wasi/release/ 下实际生成的 .wasm # 重命名为 org.apache.shenyu.plugin.wasm.base.handler.AbstractWasmPluginDataHandlerTest$TestWasmPluginDataHandler.wasm # 并放入 src/test/resources 目录(与仓库中已入库的同名文件位置一致)

随后运行该模块的测试即可触发完整链路:

mvn test -pl shenyu-plugin/shenyu-plugin-wasm-base

验证点:

  • 测试通过即代表 wasm 加载、shenyu模块导入绑定、6 个回调调用、双向内存交换全部成功;
  • 由于 WasmLoader 中WasiCtxBuilder().inheritStdout().inheritStderr()继承了标准错误流,Rust 侧的eprintln!(如rust side-> recv:hello from java 0)与 Java 侧日志(java side->hello from java 0)会出现在测试输出中,可用于人工确认数据往返方向。

七、适用前提与注意事项

  • 本文流程针对当前仓库(2.7.0-SNAPSHOT 版本线)下shenyu-plugin-wasm-base的测试样例;shenyu-plugin-wasm-base的 pom.xml 依赖shenyu-plugin-wasm-api与shenyu-plugin-base,构建测试前需保证依赖可解析;
  • 仓库中已提交改名后的.wasm二进制文件,若仅运行 Java 测试而无需改动 Rust 代码,可跳过构建步骤;只有修改了 lib.rs 逻辑后,才需要重新执行第二至四节并覆盖src/test/resources下的同名.wasm文件;
  • README 中记录的产物文件名与当前 Cargo 包名不一致,实际执行时以构建目录中真实生成的文件为准(见第三节说明);
  • wasm 文件名与类名的绑定关系由WasmLoader的clazz.getName() + ".wasm"约定强制约束,若你为自己的测试类新增内部 Handler 类,需同步保证资源文件名与其全限定名一致,否则会抛出ShenyuWasmInitException。
  • 后端
  • API网关
  • 微服务

【免费下载链接】shenyu

Apache ShenYu is a Java native API Gateway for service proxy, protocol conversion and API governance.

项目地址:https://gitcode.com/gh_mirrors/sh/shenyu
点击查看免费下载
上一篇:告别复杂API调用:Laravel HTTP客户端让外部请求如此简单
下一篇:Dream GraphQL集成指南:构建现代化API服务的完整方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 3:01:32

MindSpeed LLM支持哪些模型?Qwen3/DeepSeek/GLM等100+大模型清单全解

MindSpeed LLM支持哪些模型?Qwen3/DeepSeek/GLM等100大模型清单全解 【免费下载链接】MindSpeed-LLM 昇腾LLM分布式训练框架 项目地址: https://gitcode.com/Ascend/MindSpeed-LLM MindSpeed LLM 是面向华为昇腾(Ascend)芯片生态的大语…

作者头像 李华
网站建设 2026/9/25 2:54:43

MySQL报错only_full_group_by:原因、排查与SQL改写实战

最近群里一位老同事贴了张报错截图,红彤彤一行英文:this is incompatible with sql_modeonly_full_group_by。这大概是 MySQL 5.7 之后后端同学最常撞见的“老朋友”了。很多人第一反应是“SQL 哪里写错了”,但把 SQL 翻来覆去看,…

作者头像 李华
网站建设 2026/9/25 2:54:35

USB转I2C适配器实现400KHz总线扫描与Excel导出实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华