Rhino中Java对象层级与JavaScript原型关联问题咨询
刚好我对Rhino的Java-JS交互这块比较熟悉,来给你拆解下你遇到的这个问题~
先还原下你的场景
你有这样的Java类层级:
// 父类 public class Parent { private String parentProperty; // 对应的getter、setter我就省略啦 } // 子类,继承Parent public class Child extends Parent { private String childProperty; // 同样省略getter、setter }
为了把Child实例暴露给JavaScript表达式使用,你写了两个对应的宿主Scriptable类:
public class ParentScriptable extends ScriptableObject { private Parent parent; @Override public String getClassName() { return "Parent"; } // 处理parentProperty的获取逻辑 @Override public Object get(String name, Scriptable start) { if ("parentProperty".equals(name)) { return parent.getParentProperty(); } return super.get(name, start); } } public class ChildScriptable extends ScriptableObject { private Child child; @Override public String getClassName() { return "Child"; } // 处理childProperty的获取逻辑 @Override public Object get(String name, Scriptable start) { if ("childProperty".equals(name)) { return child.getChildProperty(); } return super.get(name, start); } }
然后你把这两个Scriptable注册到Rhino,还在全局作用域里暴露了Child实例:
Context ctx = Context.enter(); try { Scriptable globalScope = ctx.initStandardObjects(); // 注册两个Scriptable类 ScriptableObject.defineClass(globalScope, ParentScriptable.class); ScriptableObject.defineClass(globalScope, ChildScriptable.class); // 创建并初始化Child实例 Child child = new Child(); child.setParentProperty("我是父类属性"); child.setChildProperty("我是子类属性"); ChildScriptable childScriptable = new ChildScriptable(); childScriptable.child = child; // 把child对象暴露到JS全局作用域 globalScope.put("child", globalScope, childScriptable); // 执行JS代码测试 String jsCode = "print(child.childProperty); print(child.parentProperty);"; ctx.evaluateString(globalScope, jsCode, "测试脚本", 1, null); } finally { Context.exit(); }
结果你发现:如果不调用setPrototype方法,child.childProperty能正常访问,但child.parentProperty就返回undefined——这其实也符合预期,但咱们得解决这个问题对吧?
为啥会出现这个情况?
Rhino的Scriptable体系是靠原型链来实现属性查找的,和JavaScript原生的原型继承逻辑一模一样。当你在JS里访问一个对象的属性时,Rhino会先在当前Scriptable实例里找;找不到的话,就会顺着原型链往上找父类对应的Scriptable。
而你现在的代码里,ChildScriptable和ParentScriptable是完全独立的两个Scriptable类,没有建立任何原型关联。所以当JS找child.parentProperty时,只会在ChildScriptable里搜,搜不到就直接返回undefined,根本不会去ParentScriptable里找。
怎么解决?
核心就是给ChildScriptable设置原型为ParentScriptable的实例,让属性查找能顺着原型链走到父类的Scriptable里。
方式一:手动设置原型
修改你的实例化代码,加上setPrototype这一步:
Context ctx = Context.enter(); try { Scriptable globalScope = ctx.initStandardObjects(); ScriptableObject.defineClass(globalScope, ParentScriptable.class); ScriptableObject.defineClass(globalScope, ChildScriptable.class); // 先创建父类的Scriptable实例作为原型 Parent parent = new Parent(); ParentScriptable parentScriptable = new ParentScriptable(); parentScriptable.parent = parent; // 创建子类Scriptable并设置原型 Child child = new Child(); child.setParentProperty("我是父类属性"); child.setChildProperty("我是子类属性"); ChildScriptable childScriptable = new ChildScriptable(); childScriptable.child = child; // 关键代码:把父类Scriptable设为子类的原型 childScriptable.setPrototype(parentScriptable); globalScope.put("child", globalScope, childScriptable); String jsCode = "print(child.childProperty); print(child.parentProperty);"; ctx.evaluateString(globalScope, jsCode, "测试脚本", 1, null); } finally { Context.exit(); }
这样改完之后,JS访问child.parentProperty时,在ChildScriptable里找不到,就会去它的原型(ParentScriptable)里找,就能正确拿到父类属性的值了。
方式二:在子类Scriptable构造器里自动设置原型
如果你不想每次实例化都手动写setPrototype,可以在ChildScriptable的构造器里自动处理:
public class ChildScriptable extends ScriptableObject { private Child child; public ChildScriptable() { // 从全局作用域拿到Parent的原型对象 Scriptable globalScope = getParentScope(); Scriptable parentPrototype = (Scriptable) globalScope.get("Parent", globalScope); // 自动设置原型 setPrototype(parentPrototype); } @Override public String getClassName() { return "Child"; } @Override public Object get(String name, Scriptable start) { if ("childProperty".equals(name)) { return child.getChildProperty(); } return super.get(name, start); } }
这种方式利用了Rhino注册类时,会把类的构造器放到全局作用域的特性,自动获取父类的原型,让子类Scriptable自动继承父类的属性查找能力,用起来更省心。
内容的提问来源于stack exchange,提问作者Jan Moravec

