本系列文章围绕的主旨是将 JavaScript 与服务器上的 Java™ 代码结合起来,从而能够在服务器和客户机上使用相同的 JavaScript 例程。此外,本系列所展示的这些技术将让您能为 Ajax 客户机和非 Ajax 客户机 维护同一个代码库。由于服务器端的大部分代码依然用 Java 语言编写,所以有必要对 JavaScript 公开这些 Java Platform, Enterprise Edition (Java EE) 特性。在本系列中,您将了解如何在服务器端运行 JavaScript 文件、如何用 Ajax 调用远程 JavaScript 函数以及如何借助 JavaServer Pages (JSP) 技术使用这个 Java Scripting API。
典型的 Ajax 应用程序在客户端一般都使用 JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在 Web 浏览器使用 JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将 JavaScript 和服务器端的 Java 代码结合起来加以避免,而对脚本语言的完整支持可以通过 javax.script
API 获得。此外,Java SE Development Kit (JDK) 6 已经包含了 Mozilla 的 Rhino JavaScript 引擎,这意味着您将无需进行任何设置。
在本系列的第一篇文章中,将使用一个简单的脚本运行程序来在一个 Jave EE 应用程序内执行 JavaScript 文件。这些脚本将能访问被用在 JSP 页面内的所谓的 “隐式对象”,比如 application
、session
、request
和 response
。本文中的大多数示例均包含可重用代码,这样一来,您可以在自己的应用程序中轻松地将 JavaScript 应用于服务器上。
本节给出了 javax.script
API 的概览,展示了如何执行脚本来访问 Java 对象、如何从 Java 代码调用 JavaScript 函数,以及如何为所编译的脚本实现缓存机制。
javax.script
API 十分简单。可以先创建一个 ScriptEngineManager
实例,有了这个实例就能用下列方法中的任一个来获得 ScriptEngine
对象(参见清单 1):
getEngineByName()
getEngineByExtension()
getEngineByMimeType()
清单 1. 获得一个 ScriptEngine 实例
import javax.script.*; |
此外,还可以通过 getEngineFactories()
获得可用脚本引擎的列表。目前,只有 JavaScript
引擎是与 JDK 6 捆绑的,不过 ScriptEngineManager
实现了一种发现机制,能发现支持 JSR-223 Scripting for the Java Platform 的第三方引擎(参见 参考资料)。只需将脚本引擎的 JAR 文件放入 CLASSPATH 即可。
获得了 javax.script.ScriptEngine
实例后,就可以调用 eval()
来执行脚本了。也可以将 Java 对象作为脚本变量导出,其间要将 Bindings
实例传递给 eval()
方法。清单 2 所示的 ScriptDemo.java 示例导出两个名为 demoVar
和 strBuf
的变量、执行 DemoScript.js 脚本,然后让这些变量输出它们修改后的值。
清单 2. ScriptDemo.java 示例
package jsee.demo; |
DemoScript.js 文件(如清单 3 所示)包含一个 printType()
函数,该函数可用来输出每个脚本变量的类型。这个示例会调用 strBuf
对象的 append()
方法、修改 demoVar
的值并设置一个名为 newVar
的新变量脚本。
如果传递给 printType()
的对象具有 getClass()
方法,那么它一定是个 Java 对象,该对象的类名由 obj.getClass().name
获得。这个 JavaScript 表达式调用此对象的 java.lang.Class
实例的 getName()
方法。如果此对象不具备 getClass
,那么 printType()
就会调用 toSource()
方法,而该方法是所有 JavaScript 对象都有的。
清单 3. DemoScript.js 示例
println("Start script \r\n"); |
清单 4 是 ScriptDemo.java 示例的输出。值得注意的是 demoVar
作为 JavaScript String
导出,而 strBuf
的类型仍然是 java.lang.StringBuffer
。原始变量和 Java 字符串均作为本地 JavaScript 对象导出。任何其他的 Java 对象(包括数组)均原样导出。
清单 4. ScriptDemo.java 的输出
Start script |
运行该脚本后,此引擎就会接受所有变量(包括新变量)并执行反转变换,将 JavaScript 原始变量和字符串转变成 Java 对象。其他的 JavaScript 对象则被包装成 Java 对象,这些对象能使用某种特定于引擎的内部 API,比如 sun.org.mozilla.javascript.internal.NativeObject
。
有时,可能会只想使用那些标准的 API,因此 Java 代码和所执行脚本间的全部数据转换都应通过原始变量、字符串和 Java 对象(比如 bean)完成,这是因为在 JavaScript 代码内可以很容易地访问到它们的属性和方法。简言之,就是不要试图在 Java 代码内访问本地 JavaScript 对象,相反,应该在 JavaScript 代码内使用 Java 对象。
在之前的例子中,您已经看到了从 JavaScript 调用 Java 方法是可行的。现在您将会了解如何从 Java 代码调用 JavaScript 函数。首先,必须先执行包含想要调用的那个函数的脚本。然后,再将 ScriptEngine
实例强制转换为 javax.script.Invocable
,后者还提供了 invokeFunction()
和 invokeMethod()
。如果脚本实现了 Java 接口的全部方法,那么也可以使用 getInterface()
获得一个 Java 对象,该对象的方法用此脚本语言编码。
InvDemo.java 示例(如清单 5 所示)执行一个名为 InvScript.js 的脚本,它包含 demoFunction()
例程。在进行强制类型转换以将 ScriptEngine
实例转换为 Invocable
之后,这个 Java 示例才能将函数名和参数传递给引擎的 invokeFunction()
方法,而此方法会返回由 demoFunction()
返回的值。
清单 5. InvDemo.java 示例
package jsee.demo; |
InvScript.js 文件(如清单 6 所示)包含 demoFunction()
例程和之前的脚本示例所用的相同 printType()
函数。
清单 6. InvScript.js 示例
println("Start script \r\n"); |
InvDemo.java 的输出如清单 7 所示,注意到其中的数值参数均被转换成了 JavaScript 对象,并且由 demoFunction()
返回的值是作为 Java 对象获得的。这种转换只会针对原始变量和字符串进行。任何其他的对象在 JVM 和 Javascript 引擎之间都是原样传递的,反之亦然。
清单 7. InvDemo.java 的输出
Start script |
请注意 javax.script.Invocable
是一个可选接口,有些脚本引擎可能不会实现该接口。不过,JDK 6 所带的 JavaScript 引擎提供对该接口的支持。
脚本在每次执行时都进行解析会浪费 CPU 资源。在多次执行相同的脚本时,若能编译脚本,就可以显著减少执行时间,而脚本编译所需要的方法可由另外一个可选接口 javax.script.Compilable
提供,JDK 6 所带的 JavaScript 引擎亦支持该接口。
CachedScript
类(参见清单 8)接受一个脚本文件并只有当源代码有修改时才会进行重编译。getCompiledScript()
方法会调用此脚本引擎的 compile()
,进而返回 javax.script.CompiledScript
对象,该对象的 eval()
方法会执行脚本。
清单 8. CachedScript 类
package jsee.cache; |
ScriptCache
类(参见清单 9)使用 java.util.LinkedHashMap
对象为所编译的脚本实现存储库。map 的初始容量被设为所缓存脚本的最大数量并且加载系数是 1。这两个参数就确保了该 cacheMap
不需要重新处理。
默认地,LinkedHashMap
类会使用条目的插入顺序。若不想使用默认顺序,LinkedHashMap()
构造函数的第三个参数必须是 true
以便使用条目的访问顺序。
达到缓存的最大容量后,removeEldestEntry()
方法就会开始返回 true
,以便当每次向此缓存添加一个新的编译了的脚本时,一个条目都会自动从 cacheMap
删除。
通过联合使用 LinkedHashMap
的自动删除机制和访问顺序,ScriptCache
就确保了当添加了新脚本时,最近最少使用的(Least Recently Used,LRU)的脚本将能够从缓存中删除。
清单 9. ScriptCache 类
package jsee.cache; |
下一节将使用 ScriptCache
类、实现抽象的 getScriptFile()
方法并使用 getScript()
从缓存检索所编译的脚本。
在本节中,您将了解如何创建一个简单的 Java servlet 来实现 URL-脚本的映射以便能够从 Web 浏览器调用服务器端脚本。此外,servlet 还将会把几个 Java EE 对象公开为可在 JavaScript 代码内使用的变量。您还将了解如何使用脚本上下文来用单一一个 JavaScript 引擎运行多个并发的脚本。
servlet 类的名称是 JSServlet
。其 init()
方法(参见清单 10)会获得几个配置参数并创建一个 ScriptCache
对象。servlet 的脚本缓存使用 getRealPath()
获得与给定 URI 相映射的脚本文件的路径。
清单 10. JSServlet 的 init() 方法
package jsee.servlet; |
清单 11 中包含一些 servlet 的参数,这些参数在 web.xml 文件内指定。Cache-Control
头与脚本缓存毫无关系。两个头都是由 servlet 返回的此 HTTP 响应的一部分。no-cache
值告知浏览器不要缓存此 servlet 的响应,该响应应被作为 text/plain
对待。
清单 11. web.xml 文件
<web-app ...> |
从清单 11 可以看出,*.jss 模式被映射给此 servlet。这意味着 JSServlet
将会处理 URL 以 .jss 扩展名结束的所有请求。当用户在 Web 浏览器内输入这样的一个 URL 或是单击了一个 .jss 链接时,浏览器就会发送此 HTTP 请求给 Web 服务器(例如, Apache),而此服务器应该被配置成将该请求分派给 servlet 容器(比如,Tomcat)。如果 servlet 容器也充当 Web 服务器,就无需额外的配置。
当 servlet 容器获得了 URL 以 .jss 结束的这个请求时,就会调用 service()
方法,该方法是由 JSServlet
继承自 javax.servlet.http.HttpServlet
的。此方法再进而调用 doGet()
或 doPost()
,最终调用哪一个取决于此请求的 HTTP 方法。两个方法都可由 JSServlet
覆盖,这一点在本节稍后的部分还会看到。
|
每个脚本引擎实例都具有一个默认的上下文,在其中,可以用 put()
方法存储变量,而所执行的脚本的输出则被默认定向到 System.out
。在服务器环境内,常希望运行具有各自上下文的并发脚本。javax.script
API 能满足这个需求,它能提供 ScriptContext
接口和 SimpleScriptContext
实现。
Mozilla 的 Rhino JavaScript 引擎是个多线程引擎(参见侧栏),允许执行共享相同上下文的并发线程。不过,在本例中,我们想要隔离这些引擎范围以及运行在不同线程内的那些脚本的输出,这意味着必须要针对每个 HTTP 请求创建一个新的 ScriptContext
实例。
清单 12 给出了 JSServlet
类的 createScriptContext()
方法。此方法设置了上下文的 writer
属性以便在脚本执行时将脚本的输出发送给 response
对象的编写者。这意味着传递给脚本内的 print()
或 println()
的任何东西都将会包含在此 servlet 的响应内。
此外,createScriptContext()
还通过脚本上下文的 setAttribute()
方法定义了如下的脚本变量:
表 1. 由 JSServlet 执行的脚本内的可用变量
脚本变量 | 描述 |
---|---|
config |
servlet 的 javax.servlet.ServletConfig 实例 |
application |
Web 应用程序的 javax.servlet.ServletContext 实例 |
session |
javax.servlet.http.HttpSession 对象 |
request |
javax.servlet.http.HttpServletRequest 对象 |
response |
javax.servlet.http.HttpServletResponse 对象 |
out |
用于输出响应的 java.io.PrintWriter 对象 |
factory |
脚本引擎的 javax.script.ScriptEngineFactory |
factory
变量可用来获得有关 JavaScript 引擎的信息,比如语言版本或引擎版本。其余的变量的作用与它们在 JSP 页面上的无异。
清单 12. JSServlet 的 createScriptContext() 方法
public class JSServlet extends HttpServlet { |
runScript()
方法(参见清单 13)从缓存获得一个编译后的脚本并调用 eval()
方法,将给定的脚本上下文作为参数传递。
清单 13. JSServlet 的 runScript() 方法
public class JSServlet extends HttpServlet { |
可以通过调用上述的 runScript()
方法来执行 与此 HTTP 请求的 URL 相映射的那个脚本。不过,在实际的应用程序中,可能需要在运行脚本之前进行初始化并在脚本执行完后进行最后的清理工作。
在同一个上下文中,可以连续运行多个脚本。例如,一个脚本可以定义一组变量和函数,而另一个脚本则可以使用之前在相同上下文内执行的脚本的变量和函数进行一些处理。
servlet 的 handleRequest()
方法(如清单 14 所示)可设置这些 HTTP 报头、运行 init.jss 脚本、从请求的 URI 中删除此上下文路径、执行具有所获得的 URI 的脚本,然后运行另一个名为 finalize.jss 的脚本。
清单 14. JSServlet 的 handleRequest() 方法
public class JSServlet extends HttpServlet { |
JSServlet
的 doGet()
和 doPost()
方法(参见清单 15)用来调用 handleRequest()
。
清单 15. JSServletdo 的 Get() 和 doPost() 方法
public class JSServlet extends HttpServlet { |
本节包含了服务器端脚本的几个例子,向您展示了如何获得请求参数、访问 JavaBeans 的属性并生成 JSON 响应。
在前一节中,曾提及在执行所请求的脚本之前,JSServlet
会调用 init.jss(如清单 16 所示)。若想估量执行脚本所需时间,可以将开始时间存储到一个变量内,如下所示。
清单 16. init.jss 脚本
var debug = true; |
之后,可以在 finalize.jss 内(参见清单 17)计算执行时间。该时间作为 JavaScript 注释打印以便 JSServlet
能够生成有效的 JSON 响应。
清单 17. finalize.jss 脚本
var debugEndTime = java.lang.System.nanoTime(); |
本系列后面的文章将向 init.jss 和 finalize.jss 添加更多的代码。
借助 JSServlet
调用的脚本可以通过 request.getParameter()
和 request.getParameterValues()
访问请求参数,这二者会返回 Java 对象。若想使语法更简短或处理 JavaScript 对象而非 Java 字符串和数组,也不难,只需将下面这些代码行加入到 init.jss(参见清单 18)。
清单 18. 在 init.jss 内获得请求参数。
var param = new Object(); |
假设您使用清单 19 所示的 URL 请求一个名为 ParamDemo.jss 的脚本。
清单 19. 请求一个脚本的 URL 示例
http://localhost:8080/jsee/ParamDemo.jss?firstName=John&lastName=Smith |
在 ParamDemo.jss(参见清单 20)内,用 param.firstName
和 param.lastName
可以得到这两个请求参数。
清单 20. ParamDemo.jss 示例
println("Hello " + param.firstName + " " + param.lastName); |
Web 应用程序的 application
、session
和 request
范围可存储 bean 实例,可以分别使用 ServletContext
、 HttpSession
和 HttpServletRequest
的getAttribute()
和 setAttribute()
来获得或替代这些实例。也可以使用 getBean()
和 setBean()
函数(如清单 21 所示),但这两个函数必须位于 init.jss 文件内以便任何脚本均可调用它们。scope
参数应是如下字符串中的一个:
"application"
"session"
"request"
清单 21. init.jss 的 getBean() 和 setBean() 函数
function getBean(scope, id) { |
现在,假设您想要在 session
范围内保存 DemoBean
(参见清单 22)的一个实例。
清单 22. DemoBean.java 示例
package jsee.demo; |
BeanDemo.jss 脚本(如清单 23 所示)用 importPackage(Packages.jsee.demo)
导入了包含此 JavaBean 的那个包。之后,脚本试图用 getBean()
从session
范围获得这个 bean 实例。如果这个 bean 没有找到,那么 BeanDemo.jss 就会创建一个对象并利用 setBean()
将其放入 session
范围。最终,此脚本会进行增量处理并输出这个 bean 的 counter
属性。
清单 23. BeanDemo.jss 示例
importPackage(Packages.jsee.demo); |
如果所要导入的包是以 java
、javax
、org
、edu
、com
或 net
开头的,那么无须在 importPackage()
使用 Packages
前缀。此外,还可以使用importClass()
导入单个类。
清单 24 所示的示例会生成一个 JSON 响应,该响应会包含有关 JavaScript 引擎和此脚本 URI 的某些信息。此示例使用 JavaScript 语法来创建 json
对象,其源代码则用 toSource()
方法以 String
形式获得。
清单 24. JsonDemo.jss 示例
var json = { |
在本例中,从 factory
和 request
的属性中检索到的所有 Java 对象都必须转变为 JavaScript 对象,以便 toSource()
能够正常工作。清单 25 包含了此脚本的输出:
清单 25. JsonDemo.jss 的输出
({language:{name:"ECMAScript", version:"1.6"}, |
在本文中,您了解了如何使用 javax.script
API 编译和执行 JavaScript 文件。您还了解了如何基于 java.util.LinkedHashMap
实现 LRU 缓存、如何将 Java 对象作为脚本变量导出、如何实现 URL-脚本映射以及如何构建在服务器端执行 的 JavaScript 文件。请您继续关注本系列的下一篇文章,在该文章中,您将了解如何用 Ajax 调用远程 JavaScript 函数。
|
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
本文的示例应用程序 | jsee_part1_src.zip | 25KB | HTTP |
关于下载方法的信息 |
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
- JSR-223 Scripting for the Java Platform 是一个规范,其中包含了在 Java 应用程序内使用脚本语言的所有细节。
- Java Scripting Programmer's Guide 展示了这个 Java Scripting API 并提供了更多的代码示例。
- javax.script 包包含这个 Java Scripting API 所有的类和接口。
- developerWorks developerWorks Web 开发专区 富含 Web 2.0 开发所需的工具和信息。
- developerWorks Ajax 资源中心:包含不断增加的大量 Ajax 内容以及有用资源,可以让您立即开始开发 Ajax 应用程序。
获得产品和技术
- Mozilla Rhino 是一种与 Java SE Development Kit (JDK) 6 捆绑的 JavaScript 引擎。
|
Andrei Cioroianu 是 Devsphere 公司的创始人,该公司专门提供 Java EE 开发和 Web 2.0/Ajax 顾问服务。他自 1997 年就开始使用 Java 和 Web 技术,具有 10 多年解决复杂技术问题和管理商业产品的整个生命周期以及定制应用程序和开源框架的专业经验。您可以通过 www.devsphere.com 上的联系列表与 Andrei 联系。 原文地址:http://www.ibm.com/developerworks/cn/web/wa-aj-javaee/index.html#resources |