服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

mustang与rhino:java 6中的脚本编写

最新的java主版本(java se 6,又称mustang)现在正处于beta版本阶段。虽然该版本并不像java 5的更新那么多,但是它确实有一些有趣的新特性。毋庸置疑,其中的一个就是对脚本编写语言的支持。

  诸如php、ruby、javascript、python(或jython)之类的脚本编写语言被广泛应用于许多领域,并由于其灵活性和简单性而大受欢迎。由于脚本是被解释而不是被编译的,所以可以轻松地从命令行运行和测试它们。这就压缩了编码/测试周期,并提高了开发人员的生产率。脚本通常是动态键入的,其语法极富表现力,所编写出的算法要比java中的等效算法简明得多。使用起来通常也很有趣。

  在很多情况下,从java使用脚本编写语言会很有用,比如为java应用程序提供扩展,以便用户可以编写自己的脚本进行扩展或定制化核心功能。脚本编写语言可读性更强,也更容易编写,所以(从技术上来说)它们是用于为终端用户提供根据需求定制化产品的可能性的理想语言。

  早已经有许多java可用的独立脚本编写包了,包括rhino、jacl、jython、beanshell、jruby等。新消息是java 6通过一个标准接口为脚本编写语言提供了内置支持。

  java 6提供对jsr-223规范的全面支持。该规范提供了一种从java内部执行脚本编写语言的方便、标准的方式,并提供从脚本内部访问java资源和类的功能。java 6附带了与mozilla rhino的javascript 实现的内置集成。基于该规范,对诸如php、groovy和beanshell之类的其它脚本编写语言的支持也正在进行中。本文关注的是rhino实现,但是其它语言应该是基本相同的。

  脚本编写语言的名称都从何而来?由于大多数脚本编写语言都来自于开源项目,所以其名称通常都是由其各自的编写者想出来的。rhino(犀牛)的名称来自于o'reilly关于javascript的书封面上的动物。php则遵从unix自解释的惯例,是php: hypertext preprocessor的简写。jython是python脚本编写语言的java实现。而groovy只是为了显酷。

  使用脚本引擎

  jsr 223规范方便易用。要使用脚本,您只需了解一些关键类。主要是scriptengine类,它处理脚本解释和求值。要实例化一个脚本引擎,应该使用scriptenginemanager类来检索感兴趣的脚本编写语言的scriptengine对象。每种脚本编写语言都有一个名称。mozilla rhino ecmascript脚本编写语言(通常称为javascript)使用“js”进行标识。

scriptenginemanager manager = new scriptenginemanager();
scriptengine engine = manager.getenginebyname("js");

  嵌入式的javascript可用于各种用途。因为它要比硬编码的java灵活且更容易配置,所以通常还可以用于编写频繁更改的业务规则。使用eval()方法对脚本表达式进行求值。脚本编写环境中所使用的任何变量都可以使用put()方法从java代码内部赋值。  



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!

  eval()方法还接受一个reader对象,这使它容易在文件或其他外部源中保存脚本,如下例所示:

scriptenginemanager manager = new scriptenginemanager();
scriptengine engine = manager.getenginebyname("js");
engine.put("age", 21);
engine.eval(new filereader("c:/voting.js"));
检索结果

  现在可以运行脚本了,那么接下来做什么呢?通常我们都希望从脚本编写环境获取求值后的值或表达式,以便用于java代码。这有两种实现方法。第一种是使用eval()函数返回执行脚本后所返回的值。默认情况下,将返回上次执行的表达式的值。

  下例演示了一个虚构的保险公司的保险费计算方法。对于年龄小于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);
}

  返回值是上次执行的指令的值,所以在本例中就是为riskfactor所赋的值。注意,包含结果(在本例中是riskfactor)的javascript变量的值是无关的:只返回值。

  与脚本交互的第二种方式是使用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);
}


  访问java资源

  还可以从脚本内部访问java类和资源。rhino javascript引擎支持importpackage()函数,该函数允许导入java包。导入之后,就可以在脚本中实例化java对象,就像在java中所做的那样:

engine.eval("importpackage(java.util); " +
"today = new date(); " +
"print('today is ' + today);");

  调用java类上的方法也很容易做到,不管是传递给脚本引擎的对象实例,还是静态类成员。

engine.put("name","john doe");
engine.eval(
"name2 = name.touppercase();" +
"print('converted name = ' + name2);");
> converted name = john doe
可编译且可调用的引擎  




  某些脚本引擎实现支持脚本编译,这将带来相当大的性能提升。脚本可以被编译或重用,而不是在每次执行时被解释。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();

  等效的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;
}

  需要根据具体的条件计算并测试脚本编译所带来的性能提升。一些使用此处所示脚本的简单基准测试显示了大约60%的性能提升。通常,脚本越复杂,从编译中所获得的提升就应该越多。作为一个粗略的测试,我将上面的脚本以及等效的java代码运行了10000次,得到了以下的结果:

  解释后的js: 1,550ms
  编译后的js: 579ms
  编译后的java: 0.0172ms

  编译后的javascript大约比解释后的javascript运行快3倍。解释后的代码平均运行时间为15ms而编译后的代码平均运行时间为6ms。当然了,正如可以预料到的,真正编译后的java比解释后的javascript大约快了10万倍。然而,如前所述,脚本编写语言的优点在于其他地方。

  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

  这种方法允许在javascript(或其他脚本编写语言)中编写和维护库,并从一个java应用程序调用它。在交易中,重要的是要能够根据市场形势快速更新价格规则。例如,一个保险公司可能希望保险精算师能够使用一种平易的脚本编写语言直接设计和维护保险规则和保险费计算算法,随后可以从一个大型j2ee企业架构中对其进行调用。这样的架构可能包括一个在线报价系统、一个用于保险费代理程序的外部应用程序,以及后台业务应用程序,它们全都调用同一个集中式脚本。

  web开发

  jsr 223规范最伟大的目标之一是要在java web应用程序中提供非java脚本编写页面(如php)的集成性。这旨在允许将非java脚本编写页面整合为java web应用程序的一部分,同时允许从该脚本编写页面调用java类。例如,下面的php代码展示了如何从php页面中使用java对象:

//instantiate a java object

= new java( java.util.date );

//call a method
=->tostring();

//display return value
echo();
 



  更为重要的是,该规范为与java web应用服务器的集成提供了一个标准的api,用于访问和修改servlet容器会话数据:

<ul>
<?
//display session attributes in table
=->getsession()->getattributenames();
foreach ( as ) {
= ->getsession()->getattribute();
print("<li> = <li>");
}
?>
</ul>

  这种集成意义深远。现在在一个j2ee环境中,不仅可以使用java,还可以使用其它脚本编写语言来编写web应用程序,将java用作一个强大的跨平台架构。而且使用其他脚本编写语言所编写的现有页面或应用程序现在可以轻松地与j2ee应用程序进行集成。

  结束语

  一些人将脚本编写语言视为解决所有现有编程难题的答案,而另一些人则谴责它鼓励了无组织且不可维护代码的产生。像其它任何工具一样,脚本编写可以被使用或滥用。脚本语言灵活、易学,且编写起来很快。但是java ide只对其提供了有限的支持,而且难于使用诸如junit之类的传统测试框架对其进行测试,错误可能直到运行时才会出现。不过,在很多情况下,正确和适当地使用脚本编写无疑会使生活更为轻松。应该考虑以下面的方式使用脚本编写:

  作为一种扩展或定制应用程序的手段。

  作为一种实现频繁改变的灵活(有时还很复杂)的业务规则的方便方式。

  总而言之,脚本编写支持无疑为java开发人员的工具箱中有添加了一个新的得力工具。

扫描关注微信公众号