在GraalVM Polyglot上下文中从JavaScript访问Java对象

时间:2019-11-22 14:35:41

标签: java graalvm graaljs

在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"

案例2:

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 个答案:

答案 0 :(得分: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()
    );
  }
}

答案 1 :(得分:0)

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

GraalVM JavaScript ScriptEngine implementation

看看示例:

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
        );
    }
}

答案 2 :(得分:0)

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

  

默认情况下,只有使用@ 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.put(,true)。请注意,即使对Bindings#get(String)的调用也可能导致上下文初始化。以下代码显示了如何通过绑定启用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的任何调用都会强制上下文初始化。

答案 3 :(得分:0)

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

此示例取自文档...

site:yoursite.com

并且您可以像访问 java 对象一样访问此对象。

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;
    }
}