#java #jni #api-bindings #language

j4rs

j4rs代表“Java for Rust”,允许从Rust轻松调用Java代码。

34个版本 (19个重大更改)

0.20.0 2024年5月27日
0.18.0 2024年4月9日
0.17.2 2024年2月1日
0.17.1 2023年8月23日
0.1.1 2018年3月26日

#7 in 编程语言

Download history 1434/week @ 2024-05-03 1846/week @ 2024-05-10 1920/week @ 2024-05-17 2442/week @ 2024-05-24 1668/week @ 2024-05-31 1449/week @ 2024-06-07 1875/week @ 2024-06-14 778/week @ 2024-06-21 694/week @ 2024-06-28 1542/week @ 2024-07-05 1693/week @ 2024-07-12 1963/week @ 2024-07-19 1259/week @ 2024-07-26 1778/week @ 2024-08-02 1585/week @ 2024-08-09 2153/week @ 2024-08-16

7,052 每月下载量
10 个crate中使用 (9个直接使用)

MIT/Apache

2MB
8K SLoC

包含 (JAR文件,2.5MB) j4rs-0.20.0-jar-with-dependencies.jar

j4rs

crates.io Maven Central Build

j4rs代表‘Java for Rust’,允许从Rust轻松调用Java代码,反之亦然。

特性

使用方法

基础

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

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

// Create a java.lang.String instance
let string_instance = jvm.create_instance(
    "java.lang.String",     // The Java class to create an instance for
    InvocationArg::empty(), // An array of `InvocationArg`s to use for the constructor call - empty for this example
)?;

// 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
  InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// If we need to transform an `Instance` to some Rust value, the `to_rust` should be called
let rust_boolean: bool = jvm.to_rust(boolean_instance)?;
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

// Static invocation
let _static_invocation_result = jvm.invoke_static(
  "java.lang.System",     // The Java class to invoke
  "currentTimeMillis",    // The static method of the Java class to invoke
  InvocationArg::empty(), // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// Access a field of a class
let system_class = jvm.static_class("java.lang.System")?;
let system_out_field = jvm.field(&system_class, "out");

// Retrieve an enum constant using the field
let access_mode_enum = jvm.static_class("java.nio.file.AccessMode")?;
let access_mode_write = jvm.field(&access_mode_enum, "WRITE")?;

// Retrieve a nested class (note the use of `$` instead of `.`)
let state = jvm.static_class("java.lang.Thread$State")?;

Instances of Java ListMap 可以通过 java_listjava_map 函数创建

let rust_vec = vec!["arg1", "arg2", "arg3", "arg33"];

// Generate a Java List. The Java List implementation is the one that is returned by java.util.Arrays#asList
let java_list_instance = jvm.java_list(
    JavaClass::String,
    rust_vec)?;

let rust_map = HashMap::from([
    ("Potatoes", 3),
    ("Tomatoes", 33),
    ("Carrotoes", 333),
]);

// Generate a java.util.HashMap.
let java_map_instance = jvm.java_map(
    JavaClass::String,
    JavaClass::Integer,
    rust_map)?;

从Rust传递参数到Java

j4rs 使用 InvocationArg 枚举将参数传递到Java世界。

用户可以利用现有的 TryFrom 实现为几种基本类型

let i1 = InvocationArg::try_from("a str")?;      // Creates an arg of java.lang.String
let my_string = "a string".to_owned();
let i2 = InvocationArg::try_from(my_string)?;    // Creates an arg of java.lang.String
let i3 = InvocationArg::try_from(true)?;         // Creates an arg of java.lang.Boolean
let i4 = InvocationArg::try_from(1_i8)?;         // Creates an arg of java.lang.Byte
let i5 = InvocationArg::try_from('c')?;          // Creates an arg of java.lang.Character
let i6 = InvocationArg::try_from(1_i16)?;        // Creates an arg of java.lang.Short
let i7 = InvocationArg::try_from(1_i64)?;        // Creates an arg of java.lang.Long
let i8 = InvocationArg::try_from(0.1_f32)?;      // Creates an arg of java.lang.Float
let i9 = InvocationArg::try_from(0.1_f64)?;      // Creates an arg of java.lang.Double

以及 Vec

let my_vec: Vec<String> = vec![
    "abc".to_owned(),
    "def".to_owned(),
    "ghi".to_owned()];

let i10 = InvocationArg::try_from(my_vec.as_slice())?;

j4rs api 接受 InvocationArg 无论是作为引用还是值

let inv_args = InvocationArg::try_from("arg from Rust")?;
let _ = jvm.create_instance("java.lang.String", &[&inv_args])?; // Pass a reference
let _ = jvm.create_instance("java.lang.String", &[inv_args])?;  // Move

由 j4rs 返回的 Instance 可以转换为 InvocationArg 并用于调用方法

let one_more_string_instance = jvm.create_instance(
  "java.lang.String",     // The Java class to create an instance for
  InvocationArg::empty(), // The `InvocationArg`s to use for the constructor call - empty for this example
)?;

let i11 = InvocationArg::try_from(one_more_string_instance)?;

要创建一个表示 null Java值的 InvocationArg,请使用具有 Null 结构的 From 实现方法

let null_string = InvocationArg::from(Null::String);                // A null String
let null_integer = InvocationArg::from(Null::Integer);              // A null Integer
let null_obj = InvocationArg::from(Null::Of("java.util.List"));     // A null object of any other class. E.g. List

从Rust传递自定义参数到Java

对于没有 TryFrom 实现的自定义类型,也支持通过序列化。

要使用自定义结构 MyBean 作为 InvocationArg,它需要可序列化

#[derive(Serialize, Deserialize, Debug)]
#[allow(non_snake_case)]
struct MyBean {
    someString: String,
    someInteger: isize,
}

然后,可以像下面这样创建一个 InvocationArg

let my_bean = MyBean {
    someString: "My String In A Bean".to_string(),
    someInteger: 33,
};
let ia = InvocationArg::new(&my_bean, "org.astonbitecode.j4rs.tests.MyBean");

它可以用作接受 org.astonbitecode.j4rs.tests.MyBean 实例的 Java 方法的参数。

当然,为了使反序列化工作并且能够创建自定义 Java 对象,类路径中应该存在相应的 Java 类。

package org.astonbitecode.j4rs.tests;

public class MyBean {
    private String someString;
    private Integer someInteger;

    public MyBean() {
    }

    public String getSomeString() {
        return someString;
    }

    public void setSomeString(String someString) {
        this.someString = someString;
    }

    public Integer getSomeInteger() {
        return someInteger;
    }

    public void setSomeInteger(Integer someInteger) {
        this.someInteger = someInteger;
    }
}

异步支持

(从 v0.16.0 版本开始)

j4rs 通过 .async/.await 语法支持异步操作,这是通过 Jvm::invoke_async 函数实现的。该函数返回一个 Future,它将通过一个 oneshot 通道Receiver 完成。

在 Java 方面,可以通过 invoke_async 调用的方法,必须返回一个 Java Future。当 Java Future 完成,j4rs 的 Java 方面将调用原生 Rust 代码,使用创建于调用 invoke_async 时的 oneshot 通道的 Sender 来完成挂起的 Rust Future,无论是成功还是失败。

例如,假设我们有一个返回 Future 的 Java 方法

package org.astonbitecode.j4rs.tests;

public class MyTest {
  private static ExecutorService executor = Executors.newSingleThreadExecutor();

  // Just return the passed String in a Future
  public Future<String> getStringWithFuture(String string) {
    CompletableFuture<String> completableFuture = new CompletableFuture<>();
    executor.submit(() -> {
      completableFuture.complete(string);
      return null;
    });
    return completableFuture;
  }
}

我们可以像下面这样调用它

let s_test = "j4rs_rust";
let my_test = jvm.create_instance("org.astonbitecode.j4rs.tests.MyTest", InvocationArg::empty())?;
let instance = jvm.invoke_async(&my_test, "getStringWithFuture", &[InvocationArg::try_from(s_test)?]).await?;
let string: String = jvm.to_rust(instance)?;
assert_eq!(s_test, string);

请注意,对于由 invoke_async 函数调用的 Java 方法,返回 CompletableFuture 更好,因为这可以提高性能。

j4rs 使用内部的单线程 ScheduledExecutorService 通过 轮询 处理简单的 Java Futures,这些不是 CompletableFuture

这显然存在性能问题。

invoke_asyncSend

InstanceSend,并且可以安全地发送到其他线程。然而,由于 Send Approximation,由 invoke_async 返回的 Future 并不是 Send,即使它只包含一个 Instance。这是因为 Jvm 也在异步调用中被捕获,而 Jvm不是 Send

为了有一个 Future<Instance>,它 Send,可以使用 Jvm::invoke_into_sendable_async。这个函数不需要一个 Jvm 作为参数;当需要时,它会内部创建一个 Jvm,并应用一些作用域解决方案,以返回一个也是 SendFuture<Instance>

讨论 这里

类型转换

Instance 可以转换为其他类

let instantiation_args = vec![InvocationArg::try_from("Hi")?];
let instance = jvm.create_instance("java.lang.String", instantiation_args.as_ref())?;
jvm.cast(&instance, "java.lang.Object")?;

Java 数组和可变参数

// Create a Java array of Strings
let s1 = InvocationArg::try_from("string1")?;
let s2 = InvocationArg::try_from("string2")?;
let s3 = InvocationArg::try_from("string3")?;

let arr_instance = jvm.create_java_array("java.lang.String", &[s1, s2, s3])?;
// Invoke the Arrays.asList(...) and retrieve a java.util.List<String>
let list_instance = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(arr_instance)])?;

Java 泛型

// Assuming the following map_instance is a Map<String, Integer>
// we may invoke its put method
jvm.invoke(&map_instance, "put", &[InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?;

Java 基本类型

即使存在自动装箱和拆箱,j4rs 也不能使用 Integer 实例来调用带有 原始 int 参数的方法。

例如,以下代码不起作用

let ia = InvocationArg::try_from(1_i32)?;
jvm.create_instance("java.lang.Integer", &[ia])?;

它抛出了InstantiationException异常,因为Integer的构造函数需要一个原始的int类型的参数。

线程"main"中发生异常 org.astonbitecode.j4rs.errors.InstantiationException: 无法创建java.lang.Integer的实例 at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:37) Caused by: java.lang.NoSuchMethodException: java.lang.Integer.(java.lang.Integer) at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getConstructor(Class.java:2151) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:69) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:34)

在这种情况下,应该先将java.lang.Integer实例转换为原始的int类型。

let ia = InvocationArg::try_from(1_i32)?.into_primitive()?;
jvm.create_instance("java.lang.Integer", &[ia]);

Java实例链

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

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

// Create an instance
let string_instance = jvm.create_instance(
  "java.lang.String",
  &[InvocationArg::try_from(" a string ")?],
)?;

// Perform chained operations on the instance
let string_size: isize = jvm.chain(string_instance)
    .invoke("trim", InvocationArg::empty())?
    .invoke("length", InvocationArg::empty())?
    .to_rust()?;

// Assert that the string was trimmed
assert!(string_size == 8);

回调支持

j4rs提供了对Java到Rust回调的支持。

这些回调通过Rust的通道进入Rust世界。

为了初始化一个将提供Java回调值的通道,应该调用Jvm::invoke_to_channel。它返回一个包含Channel ReceiverInstanceReceiver结构体的结果。

// Invoke of a method of a Java instance and get the returned value in a Rust Channel.

// Create an Instance of a class that supports Native Callbacks
// (the class just needs to extend the 
// `org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport`)
let i = jvm.create_instance(
  "org.astonbitecode.j4rs.tests.MyTest",
  InvocationArg::empty())?;

// Invoke the method
let instance_receiver_res = jvm.invoke_to_channel(
  &i,                         // The instance to invoke asynchronously
  "performCallback",          // The method to invoke asynchronoysly
  InvocationArg::empty()      // The `InvocationArg`s to use for the invocation - empty for this example
);

// Wait for the response to come
let instance_receiver = instance_receiver_res?;
let _ = instance_receiver.rx().recv();

在Java世界中,一个能够执行本地回调的类必须扩展org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport

例如,考虑以下Java类。

performCallback方法启动了一个新的线程,并在这个线程中调用doCallback方法。该方法由NativeCallbackToRustChannelSupport类继承。

package org.astonbitecode.j4rs.tests;

import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport;

public class MyTest extends NativeCallbackToRustChannelSupport {

    public void performCallback() {
        new Thread(() -> {
            doCallback("THIS IS FROM CALLBACK!");
        }).start();
    }

}

使用Maven工件

从0.6.0版本开始,可以从Maven仓库下载Java工件。虽然可以定义更多仓库,但maven central默认总是可用的。

例如,以下是下载并部署dropbox依赖项以及使其可用于Rust代码的方法。

let dbx_artifact = MavenArtifact::from("com.dropbox.core:dropbox-core-sdk:3.0.11");
jvm.deploy_artifact(dbx_artifact)?;

还可以使用其他工件库。

let jvm: Jvm = JvmBuilder::new()
.with_maven_settings(MavenSettings::new(vec![
    MavenArtifactRepo::from("myrepo1::https://my.repo.io/artifacts"),
    MavenArtifactRepo::from("myrepo2::https://my.other.repo.io/artifacts")])
)
.build()
?;

jvm.deploy_artifact(&MavenArtifact::from("io.my:library:1.2.3"))?;

Maven工件会自动添加到类路径中,无需显式添加。

一种好的做法是在构建脚本中执行Maven工件的部署,在crate的编译期间。这确保了在Rust代码实际执行期间类路径被正确填充。

注意:部署还没有处理传递依赖。

将jar添加到类路径

如果我们有一个需要通过j4rs访问的jar,我们需要在创建JVM时将其添加到类路径中。

let entry = ClasspathEntry::new("/home/myuser/dev/myjar-1.0.0.jar");
let jvm: Jvm = JvmBuilder::new()
    .classpath_entry(entry)
    .build()?;

j4rs Java库

j4rs的jar文件可在Maven Central中找到。您可以通过在pom中添加以下依赖项来使用它:

<dependency>
    <groupId>io.github.astonbitecode</groupId>
    <artifactId>j4rs</artifactId>
    <version>0.20.0</version>
    <scope>provided</scope>
</dependency>

请注意,scopeprovided。这是因为j4rs的Java资源始终与j4rscrate一起提供。

使用以下方式以避免可能的类加载错误。

j4rs在Android上的使用

Rust端

  1. Cargo.toml中将您的crate定义为cdylib。
[lib]
name = "myandroidapp"
crate-type = ["cdylib"]
  1. 实现一个jni_onload函数,并将提供的JavaVM应用于j4rs,如下所示
const JNI_VERSION_1_6: jint = 0x00010006;

#[allow(non_snake_case)]
#[no_mangle]
pub extern fn jni_onload(env: *mut JavaVM, _reserved: jobject) -> jint {
    j4rs::set_java_vm(env);
    jni_version_1_6
}

Java端

创建一个Activity,并像下面这样定义您的本地方法。

注意:如果在较旧的Android版本中使用j4rs时遇到任何问题,这可能是由于Java 8兼容性问题造成的。这就是为什么有j4rs的Java 7版本。

<dependency>
    <groupId>io.github.astonbitecode</groupId>
    <artifactId>j4rs</artifactId>
    <version>0.13.1-java7</version>
</dependency>

更新:Java 7 已不再受支持。 j4rs 0.13.1 是最后一个版本。

JavaFX 支持

(从 v0.13.0 开始)

构建 JavaFX UI 的步骤

1. 安装 Rust、cargo 和 JDK 11(或更高版本)

2. 获取 j4rs 的 JavaFX 依赖项

最好在构建时进行此操作,以确保依赖项在 Rust 应用程序启动和 JVM 初始化时可用。这可以通过在 构建脚本 中添加以下内容来实现

	use j4rs::JvmBuilder;
use j4rs::jfx::JavaFxSupport;

fn main() {
    let jvm = JvmBuilder::new().build().unwrap();
    jvm.deploy_javafx_dependencies().unwrap();
}

3. 实现用户界面

这里有两个选择;要么使用 FXML 构建 UI,要么使用 Java 代码传统地构建 UI。在下面的代码片段中,您可能会找到每行的简短描述。

3.a 使用对 JavaFX API 的 Java 调用实现 UI
// Create a Jvm with JavaFX support
let jvm = JvmBuilder::new().with_javafx_support().build()?;

// Start the JavaFX application.
// When the JavaFX application starts, the `InstanceReceiver` channel that is returned from the `start_javafx_app` invocation
// will receive an Instance of `javafx.stage.Stage`.
// The UI may start being built using the provided `Stage`.
let stage = jvm.start_javafx_app()?.rx().recv()?;

// Create a StackPane. Java code: StackPane root = new StackPane();
let root = jvm.create_instance("javafx.scene.layout.StackPane", InvocationArg::empty())?;

// Create the button. Java code: Button btn = new Button();
let btn = jvm.create_instance("javafx.scene.control.Button", InvocationArg::empty())?;
// Get the action channel for this button
let btn_action_channel = jvm.get_javafx_event_receiver(&btn, FxEventType::ActionEvent_Action)?;
// Set the text of the button. Java code: btn.setText("Say Hello World to Rust");
jvm.invoke(&btn, "setText", &["A button that sends events to Rust".try_into()?])?;
// Add the button to the GUI. Java code: root.getChildren().add(btn);
jvm.chain(&root)?
  .invoke("getChildren", InvocationArg::empty())?
  .invoke("add", &[btn.try_into()?])?
  .collect();

// Create a new Scene. Java code: Scene scene = new Scene(root, 300, 250);
let scene = jvm.create_instance("javafx.scene.Scene", &[
  root.try_into()?,
  InvocationArg::try_from(300_f64)?.into_primitive()?,
  InvocationArg::try_from(250_f64)?.into_primitive()?])?;
// Set the title for the scene. Java code: stage.setTitle("Hello Rust world!");
jvm.invoke(&stage, "setTitle", &["Hello Rust world!".try_into()?])?;
// Set the scene in the stage. Java code: stage.setScene(scene);
jvm.invoke(&stage, "setScene", &[scene.try_into()?])?;
// Show the stage. Java code: stage.show();
jvm.invoke(&stage, "show", InvocationArg::empty())?;

3.b 使用 FXML 实现 UI

我个人更喜欢使用 FXML 构建 UI,例如使用 Scene Builder

需要注意的是,控制器类应在根 FXML 元素中定义,并且它应该是 fx:controller="org.astonbitecode.j4rs.api.jfx.controllers.FxController"

下面是一个 FXML 示例;它创建了一个带有标签和按钮的窗口

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="725.0" spacing="33.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.astonbitecode.j4rs.api.jfx.controllers.FxController">
    <children>
        <Label text="JavaFX in Rust">
            <font>
                <Font size="65.0" />
            </font>
        </Label>
        <Label text="This UI is loaded with a FXML file" />
        <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0">
            <children>
                <Button id="helloButton" mnemonicParsing="false" text="Say Hello" />
            </children>
        </HBox>
    </children>
</VBox>

元素的 id 可以用于在 Rust 中检索相应的 Nodes 并对其执行操作(例如添加事件监听器、更改文本或效果等)。

// Create a Jvm with JavaFX support
let jvm = JvmBuilder::new().with_javafx_support().build()?;

// Start the JavaFX application.
// When the JavaFX application starts, the `InstanceReceiver` channel that is returned from the `start_javafx_app` invocation
// will receive an Instance of `javafx.stage.Stage`.
// The UI may start being built using the provided `Stage`.
let stage = jvm.start_javafx_app()?.rx().recv()?;

// Set the title for the scene. Java code: stage.setTitle("Hello Rust world!");
jvm.invoke(&stage, "setTitle", &["Hello JavaFX from Rust!".try_into()?])?;
// Show the stage. Java code: stage.show();
jvm.invoke(&stage, "show", InvocationArg::empty())?;

// Load a fxml. This returns an `FxController` which can be used in order to find Nodes by their id,
// add Event Listeners and more.
let controller = jvm.load_fxml(&PathBuf::from("./fxml/jfx_in_rust.fxml"), &stage)?;

// Wait for the controller to be initialized. This is not mandatory, it is here to shoe that the functionality exists.
let _ = controller.on_initialized_callback(&jvm)?.rx().recv()?;
println!("The controller is initialized!");

// Get the InstanceReceiver to retrieve callbacks from the JavaFX button with id helloButton
let hello_button_action_channel = controller.get_event_receiver_for_node("helloButton", FxEventType::ActionEvent_Action, &jvm)?;

有关完整示例,请参阅 此处

Java 到 Rust 支持

(从 v0.12.0 开始)

  • Cargo.toml 中添加所需的两个依赖项(j4rsj4rs_derive),并将项目标记为 cdylib,以便输出共享库。此库将由 Java 代码加载和使用,以实现 JNI 调用。

  • 使用 call_from_java 属性注释将要从 Java 代码中访问的函数

#[call_from_java("io.github.astonbitecode.j4rs.example.RustSimpleFunctionCall.fnnoargs")]
fn my_function_with_no_args() {
    println!("Hello from the Rust world!");
    // If you need to have a Jvm here, you need to attach the thread
    let jvm = Jvm::attach_thread().unwrap();
    // Now you may further call Java classes and methods as usual!
}

有关完整示例,请参阅 此处

注意:JNI 在幕后使用,因此,任何针对 JNI 的命名约定都应适用于 j4rs。例如,下划线(_)应转义并成为 _1 中的 call_from_java 定义。

Rust 构建(发布 j4rs 应用程序)后的可移植性假设

在构建过程中,j4rs 创建一个 jassets 目录,其中包含 crate 工作所需的 "java world"。它总是自动填充 Java 库,可以将其视为默认类路径容器,应始终可用。

默认情况下,jassets 位于与 crate 生成的工件相同的目录中(在 CARGO_TARGET_DIR 下),因此在开发期间不应有任何问题。

但实现完成后如何发布应用程序呢?

有人可能在 Jvm 初始化期间为 j4rs 指定不同的 base_path,发出类似于以下的内容

let jvm_res = j4rs::JvmBuilder::new()
  .with_base_path("/opt/myapp")
  .build();

base_path 定义了 j4rs 运行所需两个目录的位置;即 jassetsdeps

  1. jassets 包含 j4rs jar 以及可能通过 Maven 部署的其他 jar。
  2. deps 应包含 j4rs 动态库。这是从 Java 到 Rust 进行 回调 所必需的。如果应用不执行 Java->Rust 回调,则不需要 deps 目录。

因此,某人的应用程序二进制文件可能在例如 /usr/bin 下,而 jassetsdeps 目录可能在 /opt/myapp/ 下,或者 $HOME/.myapp,或者任何其他地方。

一个示例目录树可以是

/ 
+ --- usr
|      + --- bin
|             + --- myapp
| 
+ --- opt
       + --- myapp 
              + --- jassets
              + --- deps

此外,还有一个 实用函数,它自动将特定路径下的两个目录进行复制。可以在正在打包的crate的构建脚本中调用 Jvm::copy_j4rs_libs_under 函数

Jvm::copy_j4rs_libs_under("/opt/myapp")?;

之后,/opt/myapp 将包含 j4rs 运行所需的所有内容,只要使用 with_base_path 方法创建 Jvm

let jvm_res = j4rs::JvmBuilder::new()
  .with_base_path("/opt/myapp")
  .build();

常见问题解答

我收到 java.lang.NoSuchMethodError: java.net.URLClassLoader.<init>(Ljava/lang/String;[Ljava/net/URL;Ljava/lang/ClassLoader;)V

j4rs 使用自定义的 ClassLoader,需要至少 Java 版本 9。为了使用支持旧版 Java 的默认类加载器,在构建 Jvm 时调用 JvmBuilder::with_default_classloader

我如何启用调试日志?

j4rs 使用 log crate,因此,可以根据所选择的实现相应地配置日志记录。

但是,它也支持控制台日志,这是通过设置环境变量 J4RS_CONSOLE_LOG_LEVEL 来配置的。

接受的值是 debuginfowarnerrordisabled

许可证

根据您的选择,在

依赖项

~1.7–7.5MB
~59K SLoC