You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GraalVM/GraalJS中将Java类导出为ES模块?

可以将Java对象配置为GraalJS的模块

当然可以实现。GraalJS提供了灵活的Java-JS互操作能力,你可以通过自定义模块加载逻辑,结合Java反射、Proxy机制,甚至自定义注解,让JavaScript以ES模块的方式导入并调用Java类的方法。

实现思路

核心是给GraalJS上下文配置自定义模块解析器:当JS中导入'JavaLib'时,解析器会返回一个包含Java类静态方法的JS模块对象。你可以根据需求选择直接映射方法、用Proxy动态处理调用,或是用注解筛选要导出的方法。

具体实现示例

方式一:直接映射静态方法

这种方式最直接,通过反射获取Java类的静态方法,封装成JS函数后作为模块导出。

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.Source;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class GraalJSModuleDemo {
    public static void main(String[] args) throws Exception {
        // 创建GraalJS上下文,配置允许访问指定Java类
        Context context = Context.newBuilder("js")
                .allowHostAccess(HostAccess.ALL)
                .allowHostClassLookup(className -> className.equals(JavaLib.class.getName()))
                .build();

        // 注册自定义模块解析器
        context.getEngine().getLanguages().get("js").getLanguageContext(context).getModuleRegistry()
                .registerModuleProvider(moduleName -> {
                    if ("JavaLib".equals(moduleName)) {
                        Map<String, Object> exports = new HashMap<>();
                        // 反射获取JavaLib的静态方法并封装为JS函数
                        Method fooMethod = JavaLib.class.getMethod("foo");
                        exports.put("foo", context.asValue(() -> {
                            try {
                                return fooMethod.invoke(null);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }));

                        Method barMethod = JavaLib.class.getMethod("bar");
                        exports.put("bar", context.asValue(() -> {
                            try {
                                return barMethod.invoke(null);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }));

                        return context.asValue(exports);
                    }
                    return null;
                });

        // 执行JS脚本
        Source jsSource = Source.newBuilder("js", """
                import { foo } from 'JavaLib';
                let x = foo();
                console.log(x); // 输出"foo"
                """, "test.js").build();
        context.eval(jsSource);
    }
}

方式二:用Proxy动态处理方法调用

如果Java类方法较多,或者需要动态处理调用逻辑,可以用Java的Proxy封装后暴露给JS。

// 定义对应导出方法的接口
public interface JavaLibExports {
    String foo();
    String bar();
}

// 在模块解析器中使用Proxy
// 替换方式一中的exports构建部分:
JavaLibExports proxyInstance = (JavaLibExports) java.lang.reflect.Proxy.newProxyInstance(
        JavaLibExports.class.getClassLoader(),
        new Class[]{JavaLibExports.class},
        (proxy, method, args) -> JavaLib.class.getMethod(method.getName()).invoke(null)
);

Map<String, Object> exports = new HashMap<>();
exports.put("foo", context.asValue(proxyInstance::foo));
exports.put("bar", context.asValue(proxyInstance::bar));

方式三:用注解筛选导出方法

如果想通过注解标记要导出的方法,可以自定义注解,再通过反射扫描类中带注解的方法:

// 自定义导出注解
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface ExportToJS {}

// 修改JavaLib类,给需要导出的方法加注解
public class JavaLib {
    @ExportToJS
    public static String foo() {
        return "foo";
    }

    @ExportToJS
    public static String bar() {
        return "bar";
    }

    // 该方法不会被导出
    public static String baz() {
        return "baz";
    }
}

// 在模块解析器中扫描带注解的方法
// 替换方式一中的exports构建部分:
Map<String, Object> exports = new HashMap<>();
Method[] methods = JavaLib.class.getDeclaredMethods();
for (Method method : methods) {
    if (method.isAnnotationPresent(ExportToJS.class)) {
        exports.put(method.getName(), context.asValue(() -> {
            try {
                return method.invoke(null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }));
    }
}

注意事项

  • 确保GraalJS上下文配置了足够权限(如allowHostAccess、allowHostClassLookup),否则无法访问Java类。
  • 使用ES模块时,确保JS脚本被解析为模块模式(通过import语法可自动触发)。
  • 方法调用的异常需妥善处理,避免Java异常直接暴露给JS导致崩溃。

内容的提问来源于stack exchange,提问作者lscoughlin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 13:01:21