在 Rust 中调用 Java 代码

图片来自pexels.com

j4rs 代表 “Java for Rust” ,可以在 Rust 中轻松调用 Java 代码。


不久前,我需要在 Rust 中调用 Java 代码,就启动了 j4rs 这个项目。 其主要思想是实现一个 crate,让用户能够轻松调用 Java,这样他们就可以从庞大的 Java 生态系统中受益。

我说的“容易”,是指:

  • 注意 JNI 所以需要的配置(例如 jvm 包含/链接本地共享库)。
  • 创建一个直观、简单的 API 进行 Java 调用(Rust -> Java 方向)。
  • 允许 Java -> Rust 回调。
  • 无缝在在 Linux 或者 Windows 上使用 crate(当然,前提是安装了 Java)。
  • 遵循 “Rust-first” 方式: Rust 代码可以创建和管理 JVM,而不是反过来。

在此过程中,我还发现一些 other crates 可以调用 Java 代码,但是似乎可以尽量减少程序员对 “JNI 细节” 的干预。

我们中的许多人,Rust 程序员们, 都知道如何编写 Java,但是可能没有多少人知道(或者愿意去处理)JNI的特性和陷阱。

和往常一样,安慰伴随着一些牺牲。隐藏 JNI 细节意味着使用反射和对象序列化。这带来了一些性能损失。

j4rs 是为我们这些愿意付出这个代价的人准备的。

快速开始

只需要在 Cargo.toml 中定义 j4rs

[dependencies]
j4rs = "0.6"

这里是个简单的例子,我们创建一个空的 Java String,并调用该字符串的 isEmpty() 方法。 当然,前提是在系统中安装 Java:

use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};

// Create a JVM
let jvm = JvmBuilder::new().build().unwrap();

// Create a java.lang.String instance
let string_instance = jvm.create_instance(
    "java.lang.String",	// The Java class to create an instance for
    &[]			// The InvocationArgs to use for the constructor call - empty for this example
).unwrap();

// The instances returned from invocations and instantiations
// can be viewed as pointers to Java Objects.
// They can be used for further Java calls.
// For example, the following invokes the isEmpty
// method of the created java.lang.String instance
let boolean_instance = jvm.invoke(
    &string_instance,	// The String instance created above
    "isEmpty",		// The method of the String instance to invoke
    &[],		// The InvocationArgs to use for the invocation - empty for this example
).unwrap();

// If we need to transform an Instance to Rust value,
// we use the to_rust method
let rust_boolean: bool = jvm.to_rust(boolean_instance).unwrap();
println!("The isEmpty() method of the java.lang.String instance returned {}", rust_boolean);
// The above prints:
// The isEmpty() method of the java.lang.String instance returned true

下载 Maven 构件

仅包含 JRE 的类是不够的。为了实现一些有用的东西,我们总是要调用其他 Java 库。Maven Central 包含我们需要的库。

使用 j4rs,我们可以轻松下载 Maven 构件,以便在 Rust 应用程序中使用它们。这里是一个如果部署 Apache commons-lang 的例子:

// Create a JVM
let jvm = JvmBuilder::new().build().unwrap();

// Define a Maven Artifact
let artifact = MavenArtifact::from("org.apache.commons:commons-lang3:3.9");

// Deploy the artifact in order to call it from our Rust code
jvm.deploy_maven(artifact).unwrap();

Reddit 上的一条评论指出这里的 deploy 具有误导性,因为这个词可能指 Maven 的部署阶段。这是完全正确的,因此 deploy_maven 将被启用并且重命名。

一个好的实践是,Maven 构件的部署由 build scripts 在 crate 编译期间完成。这确保类路径在 Rust 代码执行期间被正确填充。

注意: 目前,Maven部署没有考虑传递依赖关系。希望将来能得到支持。

使用 Maven 构件

现在我们使用前面提到的 Maven 构件来打印用户的 $HOME 目录:

let jvm = JvmBuilder::new().build().unwrap();

// Equivalent Java code: SystemUtils system_utils = new SystemUtils();
let system_utils = jvm.create_instance(
    "org.apache.commons.lang3.SystemUtils",
    &[]
).unwrap();

// Equivalent Java code:
// system_utils.getUserHome().getAbsolutePath();
let home_path: String = jvm.chain(system_utils)
    .invoke("getUserHome", &[]).unwrap()
    .invoke("getAbsolutePath", &[]).unwrap()
    .to_rust().unwrap();

println!("{}", home_path);

下一步

在未来,我计划开发具有支持模块 modules, 使用protocol buffers 加快序列化速度,并实现更“Jave 化”感觉的宏。

我希望你觉得这个 crate 有用。


感谢你的阅读!

原文翻译自 Announcing j4rs

发表评论

电子邮件地址不会被公开。 必填项已用*标注

2 + 2 =