这篇文章描述了如何在rhino中使用java。使用脚本调用Java有很多用途,它使得我们可以利用Java中现有的库,来帮助我们构建强大的脚本。我们可以通过编写脚本,来对Java程序进行测试。可以通过脚本来进行探索式编程,辅助Java的开发,所谓探索式编程,就是通过快速地编程调用库或API来探索这些库或API可以做什么,显而易见,脚本语言很适合探索式编程。

这里注意,ECMA标准并没有包含和Java(或者其他任何对象系统)交互的标准。本文所描述的所有内容,应该被认为是一个扩展。

 

访问 Java Packages 和 Classes

Java的每段代码都是类的一部分,每一个JAVA类都是包的一部分。在Javascript中,脚本不属于任何package。我们可以访问Java包中的类么?

Rhino定义了一个顶层的变量Packages。Packages的所有属性都是Java中顶层的包,比如java和com。比如我们可以访问java包:

js> Packages.java
[JavaPackage java]

还有一种更方便的方式,Rhino定义了一个顶层的变量java,等价于Packages.java。所以上面的例子可以更简介地写成:

js> java
[JavaPackage java]

我们可以通过访问包的下层,来直接访问java类:

js> java.io.File
[JavaClass java.io.File]

如果你的脚本需要访问很多的Java类,每次都附带完整的包名会使得编程很麻烦。Rhino提供了一个顶层的方法importPackage,它的功能和Java的import一样。比如,我们可以导入java.io包中的所有类,然后直接通过类名File来访问java.io.File:

js> importPackage(java.io)
js> File
[JavaClass java.io.File]

这里importPackage(java.io)使得java.io包中的所有类(例如File)可以在顶层被访问。这和Java中的java.io.*;等价。

要注意Java会暗中导入java.lang.*,但是Rhino不会。因为JavaScript的顶层对象Boolean、Math、Number、Object和String和java.lang包中同名的类并不相同。因为这种冲突,建议不要用importPackage来导入java.lang包。

有一点要注意的,就是Rhino对于指定包名或类名时是如何处理错误的。如果java.Myclass是可访问的,Rhino会试图加载名为java.MyClass的类,如果加载失败,它会假设java.MyClass是一个包名,不会报错:

js> java.MyClass
[JavaPackage java.MyClass]

只有在你试图将这个对象当作类使用时,才会报错。

额外的包和类

额外的包和类也可以在Rhino中使用。确认你的.jar或.class文件在你的classpath里,你就可以在你的JavaScript应用中导入它们。这些包基本不会在java包中,所以你在使用时,需要在包前加上前缀"Packages"。 比如你想导入 org.mozilla.javascript 包,你应该像下面这样去使用importPackage():

$ java org.mozilla.javascript.tools.shell.Main
js> importPackage(Packages.org.mozilla.javascript);
js> Context.currentContext;
org.mozilla.javascript.Context@bb6ab6

偶尔,我们也会见到在一些例子中使用包的完整名称,而没有使用importPackage()。这也是可以的,只是会让你多打一些字。如果使用完整的名称,上面的例子就会变成下面这样:

$ java org.mozilla.javascript.tools.shell.Main
js> jsPackage = Packages.org.mozilla.javascript;
[JavaPackage org.mozilla.javascript]
js> jsPackage.Context.currentContext;
org.mozilla.javascript.Context@bb6ab6

同样,你可以通过importClass()来导入一个类,上面的例子也可以像这样写:

$ java org.mozilla.javascript.tools.shell.Main
js> importClass(Packages.org.mozilla.javascript.Context);
js>  Context.currentContext;
org.mozilla.javascript.Context@bb6ab6

和Java一起工作

现在我们可以访问Java类,下一步就是要创建一个对象。方法就和在Java中一样, 用new来创建对象:

js> new java.util.Date()
Thu Jan 24 16:18:17 EST 2002

如果我们将创建的对象存放在JavaScript变量中,我们可以调用它的方法:

js> f = new java.io.File("test.txt")
test.txt
js> f.exists()
true
js> f.getName()
test.txt

静态方法和属性可以直接通过类对象来访问:

js> java.lang.Math.PI
3.141592653589793
js> java.lang.Math.cos(0)
1

不像Java,在JavaScript里,方法就是一个对象。它可以被评估,也可以被调用。如果我们去查看这个方法,我们可以看到这个方法所有重载的形式:

js> f.listFiles
function listFiles() {/*
java.io.File[] listFiles()
java.io.File[] listFiles(java.io.FilenameFilter)
java.io.File[] listFiles(java.io.FileFilter)
*/}

输出告诉我们,File类有listFiles方法的三种重载:一种不包含参数的,另一种包含一个FilenameFilter类型的参数,第三个包含一个FileFilter类型的参数。所有的方法都返回一个File对象数组。可以观察到Java方法的参数和返回类型在探索式编程中是非常有用的,尤其是在对一个方法的参数和返回对象不确定的时候。

另一个有助于探索式编程的特性,是可以看到对象中定义的所有方法和属性。用JavaScript的for..in , 我们可以打印这些值:

js> for (i in f) { print(i) }
exists
parentFile
mkdir
toString
wait
[44 others]

注意这里不仅列出了File类中的所有方法,也列出了从基类java.lang.Object中继承的方法,例如wait。这使得我们可以更好地处理那些有复杂继承关系的对象,因为我们可以看到对象中所有可用的方法。

Rhino可以通过属性名来方便地访问JavaBean的属性。一个JavaBean的属性foo被方法getFoo和setFoo定义,另外,一个也叫foo的boolean类型的属性,可以被isFoo来定义。比如, 下面的代码实际上调用了File对象的getName和isDirectory方法。

js> f.name
test.txt
js> f.directory
false

调用重载方法

 根据参数类型选择调用方法的过程称为重载决议。在 Java 中, 重载决议在编译时执行, 而在rhino中则在运行时发生。这种差异是不可避免的, 因为 JavaScript 使用动态类型, 在2章中: 由于变量的类型直到运行时才知道, 才会发生重载决议。

 
例如, 请查看下面的 Java 类, 它定义了许多重载方法并调用它们。 

 

public class Overload {

    public String f(Object o) { return "f(Object)"; }
    public String f(String s) { return "f(String)"; }
    public String f(int i)    { return "f(int)"; }

    public String g(String s, int i) { return "g(String,int)"; }
    public String g(int i, String s) { return "g(int,String)"; }

    public static void main(String[] args) {
        Overload o = new Overload();
        Object[] a = new Object[] { new Integer(3), "hi", Overload.class };
        for (int i = 0; i != a.length; ++i)
            System.out.println(o.f(a[i]));
    }
}

当我们编译和执行程序, 它产生输出

f(Object)
f(Object)
f(Object)

但是, 如果我们编写一个类似的脚本

var o = new Packages.Overload();
var a = [ 3, "hi", Packages.Overload ];
for (var i = 0; i != a.length; ++i)
    print(o.f(a[i]));

并且运行它,将会输出

f(int)
f(String)
f(Object)

因为Rhino在运行时选择重载方法, 所以它会调用与该参数匹配的更具体的类型。 同时, 在编译时, Java 只在参数的类型上选择重载方法。

尽管这有利于选择一种方法,这种方法可能是每个调用的更好匹配,但它确实对性能有影响,因为每次调用时都要做更多的工作。事实上,这种性能代价在实际应用中并不明显。

因为重载决议发生在运行时,它可能在运行时失败。例如,如果我们用两个整数调用重载方法g,我们就会得到一个错误,因为方法的两个形式都比另一个更接近参数类型:

js> o.g(3,4)
js:"<stdin>", line 2: The choice of Java method Overload.g
matching JavaScript argument types (number,number) is ambiguous;
candidate methods are: 
class java.lang.String g(java.lang.String,int)
class java.lang.String g(int,java.lang.String)

http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html 提供了一个更精确的重载语义定义

  实现Java接口

 

现在我们可以访问Java类,创建Java对象,并访问这些对象的字段、方法和属性,我们就可以轻松掌握大量的功能。但是,在少数情况下是不够用的:Java中的很多API通过提供客户端必须实现的接口来工作。其中一个例子就是Thread类:其构造函数Runnable包含一个run方法,这个方法在新线程启动时被调用。

为了满足这种需求,Rhino提供了创建新的Java对象实现的接口的能力。首先,我们必须定义一个JavaScript对象,其中的函数属性的名称与Java接口所需的方法名称相匹配。要实现一个Runnable ,我们只需要定义一个不带参数的run单方法。如果你还记得第3章,可以用{ propertyName: value}符号定义一个JavaScript对象。我们可以在这里结合函数表达式使用这个语法来用一个 run方法定义一个JavaScript对象:

js> obj = { run: function () { print("\nrunning"); } }
[object Object]
js> obj.run()

running

 

现在我们可以通过构建一个 Runnable 来实现 Runnable 接口的对象:

js> r = new java.lang.Runnable(obj);

 

js> r = new java.lang.Runnable(obj);
[object JavaObject]

在Java中,不可能在接口上使用new运算符,因为没有可用的实现。Rhino从JavaScript对象中获取实现obj现在我们有一个对象实现Runnable,我们可以创建Thread并运行它。我们定义的函数run 将在新线程上调用。

js> t = new java.lang.Thread(r)
Thread[Thread-2,5,main]
js> t.start()
js>

running

 

最终js提示和新线程的输出可能以任意顺序显示,具体取决于线程调度。

在后台,Rhino为一个新的Java类生成字节码,该类实现 Runnable 并转发对其 run方法的所有调用,并转发给关联的JavaScript对象。实现此类的对象称为Java适配器。因为转发到JavaScript是在运行时发生的,所以可能会延迟定义实现接口的方法直到它们被调用。虽然省略必要的方法对大编程来说是一种糟糕的做法,但它对小脚本和探索性编程很有用。

 

JavaAdapter构造函数

在前面的章节中,我们使用 new 运算符与Java接口创建Java适配器。这种方法有其局限性:不可能实现多个接口,也不能扩展非抽象类。因为这些原因,有一个 JavaAdapter 构造函数。

JavaAdapter构造函数的语法是:

new JavaAdapter(javaIntfOrClass, [javaIntf, ..., javaIntf,] javascriptObject)

这里javaIntfOrClass是一个实现的接口或一个扩展的类,并且javaIntf是实现接口的接口。而javascriptObject 则包含从Java适配器调用的方法的JavaScript对象。

在实践中,几乎不需要JavaAdapter 直接调用构造函数。大多数情况下,使用new运算符之前的语法就足够了。

作为Java接口的JavaScript函数

通常我们只需要使用一种方法实现一个接口,就像前面的 Runnable 例子或者提供各种事件监听器实现一样。为了方便这个,Rhino允许在这种接口传递JavaScript函数。该函数被称为接口方法的实现。

这里是简化的 Runnable 实例:

js> t = java.lang.Thread(function () { print("\nrunning"); });
Thread[Thread-0,5,main]
js> t.start()
js> 
running

如果所有的方法都具有相同的签名,Rhino还允许使用JavaScript函数作为Java接口的实现方法。当调用函数时,Rhino将方法的名称作为附加参数传递。函数可以使用它来代表被调用的方法:

js> var frame = new Packages.javax.swing.JFrame();
js> frame.addWindowListener(function(event, methodName) {
	if (methodName == "windowClosing") {     
            print("Calling System.exit()..."); java.lang.System.exit(0);
	}
    });
js> frame.setSize(100, 100);
js> frame.visible = true;
true
js> Calling System.exit()...

创建Java数组

Rhino不提供创建Java数组的特殊语法。你必须使用这个 java.lang.reflect.Array 类来达到这个目的。要创建一个由五个Java字符串组成的数组,可以进行以下调用:

js> a = java.lang.reflect.Array.newInstance(java.lang.String, 5);
[Ljava.lang.String;@7ffe01

要创建一个基本类型数组,我们必须使用 java.lang 包中相关对象类中定义的特殊TYPE字段。例如,要创建一个字节数组,我们必须使用特殊字段 java.lang.Byte.TYPE:

js> a = java.lang.reflect.Array.newInstance(java.lang.Character.TYPE, 2);
[C@7a84e4

而且结果值是允许被使用在该类型的Java数组的任何地方。

js> a[0] = 104
104
js> a[1] = 105
105
js> new java.lang.String(a)
hi

Java字符串和JavaScript字符串

请记住,Java字符串和JavaScript字符串是一样的。Java字符串类型的实例,java.lang.String ,并具有由该类定义的所有方法。JavaScript字符串具有由...定义的方法,String.prototype. 最常见的绊脚石是 length, 这是Java字符串方法和JavaScript字符串的动态属性:

js> javaString = new java.lang.String("Java")
Java
js> jsString = "JavaScript"
JavaScript
js> javaString.length()
4
js> jsString.length
10

Rhino 在减少这两种类型之间的差异方面提供了一些帮助。首先,您可以将JavaScript字符串传递给需要Java字符串的Java方法,Rhino将执行转换。实际上,我们在前面java.lang.String 例子中构造函数调用中看到了这个特性

如果java.lang.String 类尚未定义它们,Rhino还会使JavaScript方法可用于Java字符串。例如:

js> javaString.match(/a.*/)
ava

JavaImporter 构造函数

JavaImporter是一个新的全局构造函数,它允许在脚本化Java时省略显式的包名称:

var SwingGui = JavaImporter(Packages.javax.swing,
                            Packages.javax.swing.event,
                            Packages.javax.swing.border,
                            java.awt.event,
                            java.awt.Point,
                            java.awt.Rectangle,
                            java.awt.Dimension);
...

with (SwingGui) {
    var mybutton = new JButton(test);
    var mypoint = new Point(10, 10);
    var myframe = new JFrame();
...
}

以前,这样的功能仅适用于将 org.mozilla.javascript.ImporterTopLevel 用作顶级作用域的嵌入。这个类提供额外的 importPackage() 和importClass() 全局函数的脚本,但其广泛的使用有污染Java类名的全局命名空间的趋势,还有防止垃圾收集加载类。

详情请参阅 Bugzilla 245882.

Java 异常

 

JavaScript代码使用 try ... catch 语句可以捕获Java方法抛出的异常Rhino将Java异常封装到具有以下属性的错误对象中:

  • javaException:Java方法抛出的原始异常
  • rhinoException:由Rhino运行时包装的异常

instanceof运算符可用于查询异常的类型:

 

try { 
    java.lang.Class.forName("NonExistingClass"); 
} catch (e) {
    if (e.javaException instanceof java.lang.ClassNotFoundException) {
       print("Class not found");
    }
}

Rhino 还支持对 try... catch 语句的扩展,允许定义条件捕获异常:

function classForName(name) {
    try {
        return java.lang.Class.forName(name);
    } catch (e if e.javaException instanceof java.lang.ClassNotFoundException) {
        print("Class " + name + " not found");
    } catch (e if e.javaException instanceof java.lang.NullPointerException) {
        print("Class name is null");
    }
}

classForName("NonExistingClass");
classForName(null);

文档标签和贡献者

此页面的贡献者: Montaro2017, NoneUnique, hujun
最后编辑者: Montaro2017,