通过JNI在Rust中集成Java遇报错,求排查与解决方案
问题:Rust通过JNI调用Java类失败的排查与解决
背景
我需要把一个Java编写的库集成到Rust crate里,所以写了HelloWorld示例,用Rust的JNI bindings库实现从Rust调用Java函数,参考了官方文档和相关问题,但一直跑不起来。
项目结构
|main/java/org/hello/ |-HelloWorld.java |-HelloWorld.class |src/ |-lib.rs |-main.rs |target/ |Cargo.toml |org_hello_HelloWorld.h
编译步骤
编译Java类命令(从hello文件夹执行):
javac HelloWorld.java
生成C头文件命令(从项目根目录执行):
javac main/java/org/hello/HelloWorld.java -h .
代码实现
HelloWorld.java
package org.hello; public class HelloWorld { private static native String hello(String input); static { System.loadLibrary("java-ffi"); } public static void main(String[] args) { System.out.println("Hello world from java"); } }
lib.rs(JNI绑定)
use jni::JNIEnv; use jni::objects::{JClass, JString}; #[no_mangle] #[allow(non_snake_case)] pub extern "system" fn Java_org_hello_HelloWorld_hello(env: JNIEnv, class: JClass, _s: JString) { let result = env.call_method(class, "main", "()V", &[]).unwrap(); println!("{:#?}", result); }
main.rs(调用逻辑)
mod lib; use jni::objects::JString; use jni::{InitArgsBuilder, JNIVersion, JavaVM}; fn main() { let jvm_args = InitArgsBuilder::new() .version(JNIVersion::V8) .option("-Xcheck:jni") .build() .unwrap(); let jvm = JavaVM::new(jvm_args).unwrap(); let _guard = jvm.attach_current_thread().unwrap(); let env = jvm.get_env().unwrap(); let class = env .find_class("org/hello/HelloWorld") .expect("Error on class"); let s = env.new_string("").unwrap(); lib::Java_org_hello_HelloWorld_hello(env, class, s); }
Cargo.toml
[package] name = "java-ffi" version = "0.1.0" edition = "2021" [lib] crate_type = ["cdylib"] [dependencies] [dependencies.jni] version = "0.19" features = [ "invocation", "default" ]
初始错误
执行cargo run报错:
thread 'main' panicked at 'Error on class: JavaException', src/main.rs:20:10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Exception in thread "Thread-0" java.lang.NoClassDefFoundError: org/hello/HelloWorld Caused by: java.lang.ClassNotFoundException: org/hello/HelloWorld at java.net.URLClassLoader.findClass(URLClassLoader.java:387) at java.lang.ClassLoader.loadClass(ClassLoader.java:418) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352) at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
尝试用java命令运行(从项目根目录):
java -Djava.library.path=target/debug main.java.org.hello.HelloWorld
报错:
Error: Could not find or load main class main.java.org.hello.HelloWorld
更新后的情况
修改Java类包名为main.java.org.hello后,执行cargo run报错:
thread 'main' panicked at 'Error on class: JavaException', src/main.rs:20:10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace Exception in thread "Thread-0" java.lang.UnsatisfiedLinkError: no java-ffi in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860) at java.lang.Runtime.loadLibrary0(Runtime.java:871) at java.lang.System.loadLibrary(System.java:1124) at main.java.org.hello.HelloWorld.<clinit>(HelloWorld.java:7)
用java命令运行:
java -Djava.library.path=target/debug/ main.java.org.hello.HelloWorld
报错:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no java-ffi in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860) at java.lang.Runtime.loadLibrary0(Runtime.java:871) at java.lang.System.loadLibrary(System.java:1124) at main.java.org.hello.HelloWorld.<clinit>(HelloWorld.java:7)
问题分析与解决
1. 类路径错误(初始问题)
JVM找不到org/hello/HelloWorld是因为类路径未正确设置:
- Java类的包名应该和目录结构对应,原代码
package org.hello;是正确的(对应main/java/org/hello/) - 启动JVM时需要指定类路径(classpath),让JVM能找到编译后的
.class文件
修改main.rs中的JVM参数,添加类路径:
let jvm_args = InitArgsBuilder::new() .version(JNIVersion::V8) .option("-Xcheck:jni") // 添加类路径指向main/java目录 .option("-cp") .option("./main/java") .build() .unwrap();
2. 动态库加载错误(更新后问题)
UnsatisfiedLinkError是因为:
- 动态库的命名规则随系统不同而变化:
- Linux:
libjava-ffi.so - macOS:
libjava-ffi.dylib - Windows:
java-ffi.dll
- Linux:
System.loadLibrary("java-ffi")会自动添加前缀后缀,但需要确保库文件在java.library.path指定的路径下
解决步骤:
- 编译Rust库:
cargo build,生成的库在target/debug/目录下 - 设置环境变量指定库路径(以Linux为例):
export LD_LIBRARY_PATH=./target/debug:$LD_LIBRARY_PATH - 或者直接使用绝对路径加载库(测试用):
修改Java类中的静态块:static { System.load("/path/to/your/project/target/debug/libjava-ffi.so"); }
3. 正确的Java运行命令
当直接用java命令运行时,类路径应该指向main/java/目录(对应包名org.hello),命令应为:
# 从项目根目录执行 java -cp main/java/ -Djava.library.path=target/debug org.hello.HelloWorld
4. 额外注意点
- 确保JNI函数签名完全匹配:
Java_org_hello_HelloWorld_hello对应包名org.hello、类名HelloWorld、方法名hello,修改包名后需要同步更新Rust中的函数名 - 调用Java的
main方法时,需要传递String[]参数,原代码直接传空数组会导致参数不匹配,修复后的调用代码:// 创建空的String数组 let string_class = env.find_class("java/lang/String").unwrap(); let args_array = env.new_object_array(0, string_class, JObject::null()).unwrap(); let result = env.call_static_method( class, "main", "([Ljava/lang/String;)V", &[JValue::Object(args_array)] ).unwrap();
最终可运行的代码调整
HelloWorld.java(恢复正确包名)
package org.hello; public class HelloWorld { private static native String hello(String input); static { System.loadLibrary("java-ffi"); } public static void main(String[] args) { System.out.println("Hello world from java"); } }
main.rs(添加类路径+修复main方法调用)
mod lib; use jni::objects::{JObject, JString}; use jni::{InitArgsBuilder, JNIVersion, JavaVM, JValue}; fn main() { let jvm_args = InitArgsBuilder::new() .version(JNIVersion::V8) .option("-Xcheck:jni") // 添加类路径指向main/java目录 .option("-cp") .option("./main/java") .build() .unwrap(); let jvm = JavaVM::new(jvm_args).unwrap(); let _guard = jvm.attach_current_thread().unwrap(); let env = jvm.get_env().unwrap(); let class = env .find_class("org/hello/HelloWorld") .expect("Error on class"); // 调用main方法需要传递String[]参数 let string_class = env.find_class("java/lang/String").unwrap(); let args_array = env.new_object_array(0, string_class, JObject::null()).unwrap(); let result = env.call_static_method( class, "main", "([Ljava/lang/String;)V", &[JValue::Object(args_array)] ).unwrap(); println!("{:#?}", result); }
编译与运行步骤
- 编译Java类:
cd main/java/org/hello javac HelloWorld.java - 编译Rust库:
cargo build - 设置环境变量(Linux为例):
export LD_LIBRARY_PATH=./target/debug:$LD_LIBRARY_PATH - 运行Rust程序:
cargo run
内容的提问来源于stack exchange,提问作者al3x
相关产品推荐
相关产品推荐

