Java 脚本 规格严格

如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研发人员的工具箱中有添加了一个新的得力工具。
原文地址:https://www.cnblogs.com/diyunpeng/p/1653341.html