2

在 GraalVM CE 上运行。

openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment (build 11.0.5+10-jvmci-19.3-b05-LTS)
OpenJDK 64-Bit GraalVM CE 19.3.0 (build 11.0.5+10-jvmci-19.3-b05-LTS, mixed mode, sharing)

情况1:

import org.graalvm.polyglot.Context;

public class Test {

    static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
    }

    public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());

        context.eval("js", "var x = d.name");

        System.out.println(
                context.getBindings("js").getMember("x").asString()
        );
    }
}

结果:

null

为什么?

据我所知,d正确通过:

((Data) context.getBindings("js").getMember("d").as(Data.class)).name

返回"HelloWorld"

案例二:

context.eval("js", "d.getName()");

例外

Exception in thread "main" TypeError: invokeMember (getName) 
on JavaObject[task.Test$Data@35a3d49f (task.Test$Data)] failed due to: 
Unknown identifier: getName

但是getName是公开的……怎么了?

4

4 回答 4

2

您需要使用@HostAccess.Export注释类字段和方法

默认情况下,guest language 只能访问带有 @HostAccess.Export 注释的公共类、方法和字段。在构建上下文时,可以使用 Context.Builder.allowHostAccess(HostAccess) 自定义此策略。

使用 JavaScript 中的 Java 对象的示例:

 public class JavaRecord {
     @HostAccess.Export public int x;    
     @HostAccess.Export
     public String name() {
         return "foo";
     }
 }

或者,您可以使用 GraalVM JSR-223 ScriptEngine

GraalVM JavaScript 提供了一个符合 JSR-223 的 javax.script.ScriptEngine 实现。请注意,此功能是出于遗留原因提供的,以便更轻松地迁移当前基于 ScriptEngine 的实现。我们强烈鼓励用户使用 org.graalvm.polyglot.Context 接口

要通过 Bindings 设置选项,请在初始化引擎的脚本上下文之前使用 Bindings.put(, true)。请注意,即使调用 Bindings#get(String) 也可能导致上下文初始化。以下代码展示了如何通过 Bindings 启用 polyglot.js.allowHostAccess:

ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
bindings.put("polyglot.js.allowHostAccess", true);
bindings.put("polyglot.js.allowHostClassLookup", (Predicate<String>) s -> true);
bindings.put("javaObj", new Object());
engine.eval("(javaObj instanceof Java.type('java.lang.Object'));"); // would not work 

没有 allowHostAccess 和 allowHostClassLookup 如果用户在调用 bindings.put("polyglot.js.allowHostAccess", true); 之前调用例如 engine.eval("var x = 1;"),则此示例将不起作用,因为对 eval 的任何调用强制上下文初始化。

于 2019-11-25T06:18:20.403 回答
1

当您使用上下文并向其中添加 Java 对象时,在幕后,TruffleApi 中的 IntropLibrary 会创建一个 HostObject 并将其与该对象相关联。这意味着您不使用对象本身,而是使用包装对象。

当您调用 getMember() 方法时,IntropLibrary 只能访问公开可用的托管对象的字段和方法。由于您的内部类具有默认访问权限(无访问修饰符),因此 API 无法找到其成员,即使它们是公共的。(类的成员不能拥有比其类本身更广泛的访问权限)。

要解决这个问题,你所要做的就是让你的内部类公开

import org.graalvm.polyglot.Context;

public class Test {

  public static class Data {
    public String name = "HelloWorld";
    public String getName() {
        return this.name;
    }
  }

  public static void main(String[] args) {
    Context context = Context.newBuilder("js").allowHostAccess(true).build();
    context.getBindings("js").putMember("d", new Data());

    context.eval("js", "var x = d.name;");

    System.out.println(
        context.getBindings("js").getMember("x").asString()
    );
  }
}
于 2019-11-25T13:31:48.353 回答
0

要以通常的 js 方式获得对 java 对象的完全访问权限,您可以使用sj4js库。

此示例取自文档...

public class TestObject extends JsProxyObject {
    
    // the property of the object
    private String name = "";
    
    // the constructor with the property 
    public TestObject (String name) {
        super ();
        
        this.name = name;
        
        // we hvae to initialize the proxy object
        // with all properties of this object
        init(this);
    }

    // this is a mandatory override, 
    // the proxy object needs this method 
    // to generate new objects if necessary
    @Override
    protected Object newInstance() {
        return new TestClass(this.name);
    }
    
    // the setter for the property name
    public void setName (String s) {
        this.name = s;
    }
    
    // the getter for the property name
    public String getName () {
        return this.name;
    }
}

您可以像访问 java 对象一样访问该对象。

try (JScriptEngine engine = new JScriptEngine()) {
    engine.addObject("test", new TestClass("123"));
            
    engine.exec("test.name");
    // returns "123"

    engine.exec("test['name']")
    // returns "123"

    engine.exec("test.name = '456'")   
    engine.exec("test.name");
    // returns "456"
}
于 2021-03-09T19:47:51.737 回答
0

GraalVM JavaScript 默认强制执行严格的沙盒规则,其中之一是 JavaScript 代码无法访问主机 Java 对象,除非用户明确允许。允许您的代码访问context.eval("js", "d.getName()")的最简单方法是传递选项polyglot.js.allowAllAccess=true,如下面的链接所述:

GraalVM JavaScript ScriptEngine 实现

看看样本:

import org.graalvm.polyglot.Context;

public class Test {

    static class Data {
        public String name = "HelloWorld";
        public String getName() {
            return this.name;
        }
    }

    public static void main(String[] args) {
        Context context = Context.newBuilder("js").allowHostAccess(true).build();
        context.getBindings("js").putMember("d", new Data());

        context.eval("js", "var x = d.getName()");

        System.out.println(
                context.getBindings("js").getMember("d").as(Data.class)).name
        );
    }
}
于 2019-11-24T19:16:10.657 回答