简介:
把JavaScript脚本与服务器上Java代码相结合,从而获得在服务器端和客户端都能够自由使用的JavaScript脚本。另外,经过这一系列的被展现技术,无论是基于Ajax还是非Ajax的客户端,都将允许你维护一个单一的代码,因为大多数的服务器端的代码依然是用Java来编写的,同时你还会发现公开给JavaScript的Java EE(Java企业版)的功能特征。在这一系列的技术中,将学习一下内容
1.怎样在服务器端运行JavaScript脚本;
2.用Ajax远程调用JavaScript的功能;
3.与JSP一起使用Java Scripting API。
典型的Ajax应用,在客户端使用JavaScript脚本语言,并且和服务器端的编程语言不同,例如在服务器端使用Java语言。这样,对于某些程序开发者就必须实现两次,在Web浏览器端使用JavaScript语言,而在服务器端使用另外一种语言。通过使用把JavaScript和服务器端的Java代码相结合,获取Javax.script API对脚本语言的完全支持,这样就可以避免代码的双重发行。另外,JDK6(Java SE Development Kit 6)中已经包含了Mazilla’s Rhino JavaScript引擎,如果使用JDK6的话,就不需要重新安装了。
在这一系列的文章的开始,通过一个简单的脚本运行器,来讲解在Java EE应用程序中怎样执行JavaScript文件。这个脚本会访问在JSP页面中被叫做”implicit
objects”的对象,例如application对象、session对象,request对象以及response对象。这个例子大部分都是由可重用的代码组成,这样在你的服务器端的应用程序中就可以很容易的使用这些代码作为起始代码。
使用Javax.script
API
这一节概要介绍Javax.script
API。将学习一下内容:
1.怎样执行访问Java对象的脚本;
2.怎样用Java代码调用JavaScript的功能;
3.为编译后的脚本实现缓存机制。
执行脚本
javax.script API很简单,通过使用下列方法,创建一个ScriptEngineManager的实例就可以开始了,这个实例可以获得一个ScriptEngine对象(代码1)。
1.getEngineByName();
2.getEngineByExtension();
3.getEngineByMineType()。
代码1:获取一个ScriptEngine实例。
importjavax.script.*;
...
ScriptEngineManager manager =newScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
...
engine.eval(...);
还可以使用getEngineFactouies()方法获得一个有效的脚本引擎的列表。当前只有一个JavaScript引擎被绑定在JDK6中,但是ScriptEngineManager实现了一种发现机制,能够主动查找发现支持Java平台的JSR-223脚本规范的第三方引擎,但是必须把第三方脚本引擎的JAR文件放到Java的CLASSPATH路径中。
获得javax.script.ScriptEngine实例之后,就可以调用eval()方法来执行脚本。还可以把Java对象作为脚本变量,通过Bindings实例传递给eva()方法。代码2包含了ScriptDemo.java的列子中,把demoVar和strBuf两个变量传递给要执行的DemoScript.js脚本,然后取得变量中的被编辑的值。
代码2:ScriptDemo.js
packagejsee.demo;
importjavax.script.*;
importjava.io.*;
publicclassScriptDemo {
publicstaticvoidmain(String args[])throwsException {
// Get the JavaScript engine
ScriptEngineManager manager =newScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
// Set JavaScript variables
Bindings vars =newSimpleBindings();
vars.put("demoVar","value set in ScriptDemo.java");
vars.put("strBuf",newStringBuffer("string buffer"));
// Run DemoScript.js
Reader scriptReader =newInputStreamReader(
ScriptDemo.class.getResourceAsStream("DemoScript.js"));
try{
engine.eval(scriptReader, vars);
}finally{
scriptReader.close();
}
// Get JavaScript variables
Object demoVar = vars.get("demoVar");
System.out.println("[Java] demoVar: "+ demoVar);
System.out.println(" Java object: "+ demoVar.getClass().getName());
System.out.println();
Object strBuf = vars.get("strBuf");
System.out.println("[Java] strBuf: "+ strBuf);
System.out.println(" Java object: "+ strBuf.getClass().getName());
System.out.println();
Object newVar = vars.get("newVar");
System.out.println("[Java] newVar: "+ newVar);
System.out.println(" Java object: "+ newVar.getClass().getName());
System.out.println();
}
}
DempScript.js文件中(代码3)包含了一个叫做printType()的函数,使用这个函数输出每个脚本变量的类型。上例中还调用了strBuf对象的append()方法,编辑了demoVar对象的值,并且设定了一个新的叫做newVar的脚本变量。
如果传递给PrintType()方法的对象有getClass()方法,那么就一定是可以用obj.getClass().name方法取得类名的Java对象,下面的JavaScript表达式调用了对象的java.lang.Class实例的getName()方法;如果对象没有getClass()方法,printType()方法就调用所有的JavaScript对象都有的toSource()方法。
代码3:DemoScript.js
println("Start script /r/n");
// Output the type of an object
functionprintType(obj) {
if(obj.getClass)
println(" Java object: "+ obj.getClass().name);
else
println(" JS object: "+ obj.toSource());
println("");
}
// Print variable
println("[JS] demoVar: "+ demoVar);
printType(demoVar);
// Call method of Java object
strBuf.append(" used in DemoScript.js");
println("[JS] strBuf: "+ strBuf);
printType(strBuf);
// Modify variable
demoVar ="value set in DemoScript.js";
println("[JS] demoVar: "+ demoVar);
printType(demoVar);
// Set a new variable
varnewVar = { x: 1, y: { u: 2, v: 3 } }
println("[JS] newVar: "+ newVar);
printType(newVar);
println("End script /r/n");
下面列出了ScriptDemo.java例子的输出结果,首先注意的是demoVar变量是作为JavaScript的String类型被报告的,而strBuf变量依然是java.lang.StringBuffer类型。对于原始变量和Java string类型的变量类型是作为JavaScript的本地对象来报告的,而对于其他的任意Java对象(包括数组对象)的类型,都会被报告为其自身的类型。
Start script
[JS] demoVar: value set in ScriptDemo.java
JS object: (new String("value set in ScriptDemo.java"))
[JS] strBuf: string buffer used in
DemoScript.js
Java object: java.lang.StringBuffer
[JS] demoVar: value set in DemoScript.js
JS object: (new String("value set in DemoScript.js"))
[JS] newVar: [object Object]
JS
object: ({x:1, y:{u:2, v:3}})
End script
[Java] demoVar: value set in DemoScript.js
Java object: java.lang.String
[Java] strBuf: string buffer used in
DemoScript.js
Java object: java.lang.StringBuffer
[Java] newVar: [object Object]
Java object: sun.org.mozilla.javascript.internal.NativeObject
脚本运行之后,引擎会带出所有的变量(包括脚本中新的变量),并进行与JavaScript相反的类型转换,把JavaScript的原始类型和strings类型转换成Java对象,其他的JavaScript对象会使用引擎内部的特殊的API,把它们封装到Java对象中,例如:sun.org.mozilla.javascript.internal.NativeObject.
因为你可能想使用唯一的标准APIs,所以所有的Java代码和被执行的脚本之间的数据交换都应该通过原始类型变量、strings类型变量以及在JavaScript代码中能够非常容易的访问到属性和方法的Java对象(如java beans)。简单的说,在Java代码中不要试图访问JavaScript对象,而是使用Java对象来替代JavaScript代码。
调用功能函数
在前面的例子中,我们已经看到在JavaScript中调用Java的方法是可能的。现在我们开始学习在Java代码中怎样调用JavaScript的功能函数。首先必须执行包含我们想要调用的函数的脚本,然后把ScriptEngine实例对象转化成javax.script.Invocable类型,这类型中提供了invokeFunction()和invokeMethod()方法。如果脚本中实现了所有的Java接口中的方法,那么还可以使用getInterface()方法来获得用脚本语言中编写的Java对象的方法。
InvDemo.java例子(代码5)中执行了一个叫做InvScript.js的脚本,脚本中包含了demoFunction()方法的实现。把ScriptEngine实例转换成Invocable类型的对象后,把函数的名字和参数传递给引擎的invokeFunction()方法,返回值是被demoFunction()方法返回的。
代码5:
packagejsee.demo;
importjavax.script.*;
importjava.io.*;
publicclassInvDemo {
publicstaticvoidmain(String args[])throwsException {
// Get the JavaScript engine
ScriptEngineManager manager =newScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
// Run InvScript.js
Reader scriptReader =newInputStreamReader(
InvDemo.class.getResourceAsStream("InvScript.js"));
try{
engine.eval(scriptReader);
}finally{
scriptReader.close();
}
// Invoke a JavaScript function
if(engineinstanceofInvocable) {
Invocable invEngine = (Invocable) engine;
Object result = invEngine.invokeFunction("demoFunction",1,2.3);
System.out.println("[Java] result: "+ result);
System.out.println(" Java object: "
+ result.getClass().getName());
System.out.println();
}else
System.out.println("NOT Invocable");
}
}
InvScript.js文件(代码6)中包含了demoFunction()函数和在前面例子的脚本文件中相同的printType()函数。
代码6:
println("Start script /r/n");
functionprintType(obj) {
if(obj.getClass)
println(" Java object: "+ obj.getClass().name);
else
println(" JS object: "+ obj.toSource());
println("");
}
functiondemoFunction(a, b) {
println("[JS] a: "+ a);
printType(a);
println("[JS] b: "+ b);
printType(b);
varc = a + b;
println("[JS] c: "+ c);
printType(c);
returnc;
}
println("End script /r/n");
如果看一下InvDemo.java的输出结果,就会发现,数字参数被转换成了JavaScript对象,demoFunction()方法的返回值是作为Java对象来获得的。这些转换仅限于原始数据类型和strings类型,任何其他的被传递的对象的类型,在JVM和JavaScript引擎之间都不会被改变,反之亦然。
输出结果:
Start script
End script
[JS] a: 1
JS object: (new Number(1))
[JS] b: 2.3
JS object: (new Number(2.3))
[JS] c: 3.3
JS object: (new Number(3.3))
[Java] result: 3.3
Java object: java.lang.Double
注意:javax.script.Invocable是一个可选接口,有些脚本引擎可能没有实现。JDK6的JavaScript引擎支持这个接口。
编译脚本
脚本每次被执行时都需要从新解释,这样会浪费CPU资源。如果相同脚本要多次运行,就可以使用另一个叫做javax.script.Compilable的可选接口来编译脚本,这样就可以明显的降低解释脚本的次数。这个可选接口在JDK6的脚本引擎中被支持。
CachedScript类(代码8)接收一个脚本文件,并且只有在脚本源代码被再次编辑时才从新编译。getCompiledScript()方法调用了脚本引擎的compile()方法,这方法返回了执行脚本的javax.script.CompiledScript对象的eval()方法。
代码8:
packagejsee.cache;
importjavax.script.*;
importjava.io.*;
importjava.util.*;
publicclassCachedScript {
privateCompilable scriptEngine;
privateFile scriptFile;
privateCompiledScript compiledScript;
privateDate compiledDate;
publicCachedScript(Compilable scriptEngine, File scriptFile) {
this.scriptEngine = scriptEngine;
this.scriptFile = scriptFile;
}
publicCompiledScript getCompiledScript()
throwsScriptException, IOException {
Date scriptDate =newDate(scriptFile.lastModified());
if(compiledDate ==null|| scriptDate.after(compiledDate)) {
Reader reader =newFileReader(scriptFile);
try{
compiledScript = scriptEngine.compile(reader);
compiledDate = scriptDate;
}finally{
reader.close();
}
}
returncompiledScript;
}
}
ScriptCache类中(代码9)还使用Java.util.LinkedHashMap对象为被编译的脚本实现了一个脚本仓库。这个map的初始容量被设定为缓存脚本的最大数,并且装载系数是1,这两个参数保证了cacheMap不必重新进行哈希计算。
默认条件下,LinkedHashMap类为其内部实体采用插入顺,因此比需把LinkedHashMap()构造器的第三个参数设定为true,这样map中的实体对象就可以用访问顺来代替默认顺。
到达缓存的最大容量以后,removeEldestEntry()方法开始返回true,使得每次有新的被编译的脚本添加到缓存中时,一个脚本实体能够自动的从cacheMap中删除。
通过LinkedHashMap的自动删除机制和访问顺相结合,在有新的脚本被添加时,ScriptCache确保最近使用的脚本从整个缓存中被删除。
代码9:
packagejsee.cache;
importjavax.script.*;
importjava.io.*;
importjava.util.*;
publicabstractclassScriptCache {
publicstaticfinalString ENGINE_NAME ="JavaScript";
privateCompilable scriptEngine;
privateLinkedHashMap cacheMap;
publicScriptCache(finalintmaxCachedScripts) {
ScriptEngineManager manager =newScriptEngineManager();
scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME);
cacheMap =newLinkedHashMap(
maxCachedScripts,1,true) {
protectedbooleanremoveEldestEntry(Map.Entry eldest) {
returnsize() > maxCachedScripts;
}
};
}
publicabstractFile getScriptFile(String key);
publicsynchronizedCompiledScript getScript(String key)
throwsScriptException, IOException {
CachedScript script = cacheMap.get(key);
if(script ==null) {
script =newCachedScript(scriptEngine, getScriptFile(key));
cacheMap.put(key, script);
}
returnscript.getCompiledScript();
}
publicScriptEngine getEngine() {
return(ScriptEngine) scriptEngine;
}
}待续.....