本月重拾Java,从JSP学起。昨天为Matrix.org.cn翻译了一篇OnJava上的文章:The Mustang Meets the Rhino: Scripting in Java 6

The Mustang Meets the Rhino: Scripting in Java 6

野马遇到犀牛:在Java 6中使用脚本

(题释:野马Mustang是Java 6的代号,犀牛Rhino是Mozilla发布的Java的JavaScript脚本引擎)

by John Ferguson Smart

04/26/2006

作者:John Ferguson Smart 04/26/2006

译者:amonw 06/21/2006

The latest major Java release (Java SE 6, aka Mustang), is now in its beta version. Although this new version is not as major an update as Java 5, it does come with a few interesting new features. Undoubtedly, one of these is its support for scripting languages.

最新的JAVA主要发行版本(Java SE 6,又名野马Mustang)现在处于beta版。虽然这个新版本并不像Java 5那样是一个重要升级版本,它仍带来一些有趣的新特性。其中的一个毫无疑问是它对脚本语言的支持。

Scripting languages such as PHP, Ruby, JavaScript, Python (or Jython), and the like are widely used in many domains, and are popular because of their flexibility and simplicity. Scripts are interpreted, not compiled, so they can be easily run and tested from the command line. This tightens the coding/testing cycle, and increases developer productivity. They are generally dynamically typed, and have expressive syntaxes that allow an equivalent algorithm to be written much more concisely than in Java. They are also often fun to work with.

PHP、Ruby、JavaScript、Python(或Jython)及其它类似的脚本语言在很多领域被广泛应用,由于其灵活简便而非常流行。脚本使用解释机制而不是编译,因此它们容易通过命令行来运行和调试。这使得编码/调试周期更加紧密,提高开发者的生产效率。脚本语言通常是自动类型的,而且其易于表达的语法使得对于同样的算法,写出的程序比用Java的更简明扼要。人们常常乐在其中。

Using scripting languages from Java can be useful in many situations, such as providing extensions to your Java application so that users can write their own scripts to extend or customize the core functionalities. Scripting languages are both simpler to understand and easier to write, so they can be ideal to give (technical) end users the possibility to tailor your product to their needs.

在Java中使用脚本语言在许多情况下都非常有用,譬如可在你的Java应用中提供扩展,使用户能够自己编写脚本来扩充或定制核心功能。脚本语言既易于理解又容易编写,因此是使最终(专业)用户能够剪裁你的产品使之符合他们需求的理想工具。

Many independent scripting packages have been available for Java for some time, including Rhino, Jacl, Jython, BeanShell, JRuby, and others. The novelty is that Java 6 provides built-in support for scripting languages via a standard interface.

很多独立的脚本软件包已经为Java所用一段时间了,包括Rhino、Jacl、Jython、BeanShell、JRuby还有其它一些。新颖的是Java 6通过标准接口提供内置的对脚本语言的支持。

Java 6 provides integrated support of the JSR-223 specification. This spec provides a convenient, standard way to execute scripting languages from within Java and provides access to Java resources and classes from within scripts. Java 6 comes with a built-in integration of Mozilla's Rhino implementation of JavaScript. And based on this spec, support for other scripting languages such as PHP, Groovy, and BeanShell, is in the pipelines. Rhino implementation is the focus for this article, but other languages should be essentially the same.

Java 6 提供集成的JSR-223规范的支持。这个规范提供方便的标准的方法来从Java内部执行脚本语言,并使脚本能够访问Java的资源和类。Java 6内集成了Mozilla的Rhino以实现JavaScript。基于此规范,对于其它脚本语言如PHP、Groovy、BeanShell等的支持也在进行中。Rhino实现方式是本文的焦点,但其它语言的实现在本质上应是一致的。

Where do scripting language names come from? Well, since most scripting languages are generated from open source projects, the names generally are the product of the imagination of their respective authors. Rhino gets its name from the animal on the cover of the O'Reilly book about JavaScript. PHP, in the self-referential Unix tradition, is short for PHP: Hypertext Preprocessor. Jython is a Java implementation of the Python scripting language. And Groovy just seemed like a cool name.

脚本语言的名字是怎么来的呢?既然大部分脚本语言来自于开源项目,它们的名字来源于各自作者的想象。Rhino(犀牛)来自于一本O’Reilly关于JavaScript的书(译注:《JavaScript: The Definitive Guide》)的封面动物。PHP,按Unix自我指称的传统,是PHP: Hypertext Preprocessor(超文本处理器)的缩写。Jython是Python脚本语言的Java实现。而Groovy只是看来像个很酷的名字。

Using the Scripting Engine

使用脚本引擎

The JSR 223 specifications are convenient and easy to use. To work with scripts, you only need to know a couple of key classes. The main one is the ScriptEngine class, which handles script interpreting and evaluation. To instantiate a script engine, you use the ScriptEngineManager class to retrieve a ScriptEngine object for the scripting language you're interested in. Each scripting language has a name. The Mozilla Rhino ECMAScript scripting language (commonly known as JavaScript) is identified by "js".

JSR 223规范方便而易于使用。要使用脚本,你只需知道几个关键类。其中主要的一个是ScriptEngine类,它处理脚本的解释和执行。要实例化一个脚本引擎,你要用ScriptEngineManager类来获得一个用于你感兴趣脚本的ScriptEngine对象。每种脚本语言有一个名字。Mozilla Rhino的ECMAScript脚本语言(通常称之为JavaScript)标识为“js”。

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

Embedded JavaScript can be used for a variety of purposes. Since it is more flexible and easier to configure than hard-coded Java, it can often be used to code business rules that may change often. Scripting expressions are evaluated using the eval() method. Any variables used in the scripting environment can be assigned from within your Java code using the put() method.

内嵌的JavaScript可用于多种用途。由于它比难以编写的Java更灵活和更容易配置,常常被用于编写经常变化的业务规则。脚本表达式用eval()方法来执行。脚本环境中使用的所有变量可在你的Java代码中用put()方法赋值。

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

engine.put("age", 21);

engine.eval(

"if (age >= 18){ " +

" print('Old enough to vote!'); " +

"} else {" +

" print ('Back to school!');" +

"}");

> Old enough to vote!

The eval() method also accepts a Reader object, which makes it easy to store scripts in files or other external sources, as in the following example:

eval()方法也可接受一个Reader对象,以便把脚本保存在文件或其它外部资源中,例子如下:

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

engine.put("age", 21);

engine.eval(new FileReader("c:/voting.js"));

Retreiving Results

获取结果

So now you can run a script, what next? You will generally want to fetch calculated values or expressions from the scripting environment for use in your Java code. There are two ways to do this. The first one is that the eval() function returns the value returned by the execution of the script. By default, this is the value of the last executed expression.

现在你可以运行脚本了,下一步呢?你通常会想从脚本环境中取出计算结果值或表达式以供你的Java代码使用。有两种方法来做到这一点。第一种是eval()方法返回脚本执行的返回值,默认为最后执行的表达式的值。

The following example illustrates the premium calculations for an imaginary insurance company. Any driver less than 25 years of age will pay 50% extra. If a driver over 25 has a no claims bonus, the company offers a 25% discount. Otherwise, the standard premiums will apply. This rule could be implemented using a JavaScript expression as follows:

以下例子阐述一个虚构的保险公司的保险费的计算方式。小于25岁的司机要多支付50%;如果一个司机大于25岁而有未索赔补贴的,公司减免25%;否则,适用标准保费。此规则可用以下JavaScript表达式实现:

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

engine.put("age", 26);

engine.put("noClaims", Boolean.TRUE);

Object result = engine.eval(

"if (age < 25){ " +

" riskFactor = 1.5;" +

"} else if (noClaims) {" +

" riskFactor = 0.75;" +

"} else {" +

" riskFactor = 1.0;" +

"}");

assertEquals(result,0.75);

}

The returned value is the value of the last executed instruction, so in this case it will be the value assigned to riskFactor. Note that the name of the JavaScript variable that contains the result (in this case, riskFactor) is irrelevant: only the value is returned.

返回值是最后执行的指令的值,因此在此情况下是赋给riskFactor的值。请注意JavaScript中存放结果的变量名(在这里是riskFactor)是无关紧要的:仅仅它的值被返回。

The second way to interact with the script is to use a Bindings object. A Bindings object is essentially a map of key/value pairs that can be used to exchange information between your Java application and the JavaScript script.

第二种与脚本交互的方法是使用Bindings对象。一个Bindings对象本质上是一个键/值对的对应表,可用于在你的Java应用和JavaScript脚本之间交换信息。

public void testEvalWithBindings()

throws ScriptException {

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

Bindings bindings

= engine.createBindings();

bindings.put("age", 26);

bindings.put("noClaims", Boolean.TRUE);

bindings.put("riskFactor", 1);

engine.eval(

"if (age < 25){ " +

" riskFactor = 1.5;" +

"} else if (noClaims) {" +

" riskFactor = 0.75;" +

"} else {" +

" riskFactor = 1.0;" +

"}");

double risk = bindings.get("riskFactor");

assertEquals(risk,0.75);

}

Accessing Java Resources

访问Java资源

You can also access Java classes and resources from within scripts. The Rhino JavaScript engine supports the importPackage() function, which allows you to import Java packages. Once imported, you can instantiate Java objects within the script just as you would in Java:

你还可以从脚本内部访问Java类和资源。Rhino的JavaScript引擎支持importPackage()函数,允许你导入Java包。一旦导入以后,你可以在脚本内部实例化一个Java类,就像你在Java中做的一样:

engine.eval("importPackage(java.util); " +

"today = new Date(); " +

"print('Today is ' + today);");

Calling methods on Java classes is also easy to do, both on object instances passed to the script engine, and on static class members.

调用Java类的方法也很简单,不管是对于传入脚本引擎的实例对象还是类的静态成员。

engine.put("name","John Doe");

engine.eval(

"name2 = name.toUpperCase();" +

"print('Converted name = ' + name2);");

> Converted name = JOHN DOE

Compilable and Invocable Engines

CompilableInvocable引擎(接口)

Some script engine implementations support script compilation, which allows considerable performance gains. Scripts can be compiled and reused, rather than being interpreted at each execution. The compile() method returns a CompiledScript instance, which can then be used to evaluate the compiled expression via the eval() method:

有的脚本引擎支持脚本的编译,可带来可观的性能提升。脚本可被编译和重用,而不是每次执行时进行解释。compile()方法返回一个CompiledScript实例,可用于通过eval()执行编译后的脚本表达式:

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

Compilable compilable

= (Compilable) engine;

CompiledScript script

= compilable.compile(

"if (age < 25){ " +

" riskFactor = 1.5;" +

"} else if (noClaims) {" +

" riskFactor = 0.75;" +

"} else {" +

" riskFactor = 1.0;" +

"}");

Bindings bindings

= engine.createBindings();

bindings.put("age", 26);

bindings.put("noClaims", Boolean.TRUE);

bindings.put("riskFactor", 1);

script.eval();

The equivalent Java code would be something along the following lines:

等价的Java代码可写成:

public double calculateRiskFactor(int age, boolean noClaims) {

double riskFactor;

if (age < 25) {

riskFactor = 1.5;

} else if (noClaims) {

riskFactor = 0.75;

} else {

riskFactor = 1.0;

}

return riskFactor;

}

How much is to be gained by script compilation is something you need to evaluate and benchmark in each particular circumstance. Some simple benchmarks using the script illustrated here show performance gains of around 60%. In general, the more complex the script, the more you can expect to gain from compilation. Just as a rough test, I ran the above script 10,000 times, along with the equivalent Java code shown above. I obtained the following cumulative results:

要知道能从脚本的编译得到多少好处你得在每种特定的环境进行测试评估。通过用这里的脚本进行一些简单的检测,显示性能提升约60%。在通常情况下,脚本越复杂,你可望通过编译得到越多的性能提升。正如在一个粗略的测试中,我运行上面的脚本10000次,同时运行了上面等价的Java代码。我得到下面的累计结果:

Interpreted JS:

1,550ms

Compiled JS:

579ms

Compiled Java:

0.0172ms

解释的JS:

1,550ms

编译的JS:

579ms

编译的Java:

0.0172ms

So, the compiled JavaScript ran roughly three times faster than the interpreted JavaScript. The interpreted code took an average of 15ms to run, whereas the compiled code averaged at about 6ms. Of course, as would be expected, real compiled Java code is roughly 100,000 times faster than the interpreted JavaScript. However, as mentioned above, the advantages of scripting languages to be found elsewhere.

因此,编译后的JavaScript大概比解释的JavaScript快3倍。解释的代码平均运行15毫秒,而编译的代码平均约6毫秒。当然,正如所预料的,真正的编译Java代码比解释的JavaScript大概快上10万倍。然而,就像上面提到的,脚本语言有别的一些优势。

The Invocable interface lets you call individual functions defined in the script from your Java code. The invoke() method takes the name of the function to be invoked, and an array of parameters, and returns the result of the call.

Invocable接口使你可以从你的Java代码中调用脚本中定义的函数。invoke()方法传入要调用的函数名和参数数组,返回调用结果。

ScriptEngineManager manager

= new ScriptEngineManager();

ScriptEngine engine

= manager.getEngineByName("js");

engine.eval(

"function increment(i) {return i + 1;}");

Invocable invocable = (Invocable) engine;

Object result

= invocable.invoke("increment",

new Object[] {10});

System.out.print("result = " + result);

> result = 11

This approach allows libraries to be written and maintained in JavaScript (or in some other scripting language), and called from a Java application. In business, it is important to be able to quickly update pricing policies that are subject to market pressures. An insurance company, for example, might want actuaries to be able to directly design and maintain insurance policies and premium calculation algorithms using an easy scripting language, which could then be invoked from within a larger J2EE enterprise architecture. Such an architecture might include an online quoting system, an extranet application for insurance brokers, as well as back-office business applications, all invoking the same centralized scripts.

这种方法允许用JavaScript(或别的脚本语言)编写和维护函数库,并从Java应用中进行调用。在业务上,快速地更新受市场压力影响的价格政策非常重要。例如一家保险公司,可能希望保险计算员能使用简单的脚本语言直接设计和维护保险政策和保费的计算算法,然后能被一个大型J2EE企业架构调用。这种架构可能包括一个在线报价系统、一个保险承包商使用的外网应用和后台业务应用,所有这些都调用同样的集中脚本。

Web Development

Web开发

One of the more ambitious goals of the JSR 223 specification is to provide for integration of non-Java scripting pages (such as PHP) into a Java web application. This is designed to allow both non-Java scripting pages to be integrated as part of a Java web application, but also for Java classes to be invoked from the scripting pages. For example, the following PHP code shows how Java objects can be used from within a PHP page:

JSR 223 规范的一个更有雄心的目标是令非Java的脚本页面能整合到一个Java的Web应用中。它被设计为不但非Java的脚本页面能整合成为一个Java的Web应用的一部分,而且可以在这些脚本页面中调用Java类。例如,以下PHP代码显示如何在PHP页面中使用Java对象:

//instantiate a java object

$javadate = new Java( java.util.Date );

//call a method

$date =$javadate->toString();

//display return value

echo($date);

More importantly for integration into a Java web application server, the specification provides a standard API for accessing and modifying the servlet container session data:

对于整合进Java Web应用服务器更重要的是,这个规范提供一个标准API(应用程序接口)来访问和修改servlet容器的会话数据:

<ul>

<?

//display session attributes in table

$enum=$request->getSession()->getAttributeNames();

foreach ($enum as $name) {

$value = $request->getSession()->getAttribute($name);

print("<li>$name = $value<li>");

}

?>

</ul>

The implications of this integration are far-reaching. It is now possible to code web applications in a J2EE environment not only in Java, but also with other scripting languages, using Java as a powerful cross-platform architecture. And existing pages or applications written in other scripting languages can now be integrated with J2EE applications with little effort.

这种整合的意义深远。现在在J2EE环境除了Java以外还可以用其它脚本语言编写Web应用,而把Java作为一种强大的跨平台架构。而且已有的用其它脚本语言编写的页面或应用现在可以仅花费很小的功夫就能整合进J2EE应用中。

Conclusion

总结

Scripting languages are hailed by some as the answer to all our programming woes and decried by others as encouraging unstructured and unmaintainable code. As with any tool, scripting can be used or abused. Script languages are flexible, easy to learn and fast to write. However, they have only limited support in Java IDEs, are difficult to test using traditional testing frameworks such as JUnit, and errors may not appear until runtime. Nevertheless, correct and appropriate use of scripting can certainly make life easier in many cases.

脚本语言受到一些人的欢迎,认为是所有编程苦恼的解决方案,而又受到另一些人的反对,认为鼓励了非结构化和难以维护的代码。就像别的工具一样,脚本可能用得好也可能被滥用。脚本语言十分灵活,容易上手而且能快速编写。然而,它们在Java的IDE(集成开发环境)中只得到有限的支持,很难使用传统的调试框架如JUnit进行调试,错误只在运行时才会显现出来。尽管如此,正确和恰当地使用脚本在很多时候会为你带来方便。

Scripting should be considered in situations such as:

  • A means of extending or customizing applications.
  • A convenient way of implementing flexible (and sometimes complex) business rules that may often change.

在如下的一些情况下应考虑使用脚本:

  • 作为扩充或定制应用的一种方法
  • 实现经常修改的灵活的(有时候是复杂的)业务规则的一种便利途径

Overall, scripting support undoubtedly offers a rich new addition to the Java developer's toolbox.

总而言之,对脚本的支持无疑大大充实了Java开发人员的工具箱。

Resources

资源

John Ferguson Smart has been involved in the IT industry since 1991, and in J2EE development since 1999.

John Ferguson Smart1991年开始涉足IT行业,1999年开始从事J2EE开发工作