典型的 Ajax 应用程序在客户端一般都使用 JavaScript,而在服务器端常常使用另外一种语言,比如 Java。因此,开发人员必须将其中一些例程实现两次,一次用于在 Web 浏览器使用 JavaScript,另一次用于在服务器使用另外一种语言。这种双重编码问题实际上可以通过将 JavaScript 和服务器端的 Java 代码结合起来加以避免,而对脚本语言的完整支持可以通过 在本系列的第一篇文章中,将使用一个简单的脚本运行程序来在一个 Jave EE 应用程序内执行 JavaScript 文件。这些脚本将能访问被用在 JSP 页面内的所谓的 “隐式对象”,比如 使用 javax.script API本节给出了 执行脚本
清单 1. 获得一个 ScriptEngine 实例import javax.script.*; ... ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); ... engine.eval(...); 此外,还可以通过 获得了 清单 2. ScriptDemo.java 示例package jsee.demo; import javax.script.*; import java.io.*; public class ScriptDemo { public static void main(String args[]) throws Exception { // Get the JavaScript engine ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); // Set JavaScript variables Bindings vars = new SimpleBindings(); vars.put("demoVar", "value set in ScriptDemo.java"); vars.put("strBuf", new StringBuffer("string buffer")); // Run DemoScript.js Reader scriptReader = new InputStreamReader( 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(); } } DemoScript.js 文件(如清单 3 所示)包含一个 如果传递给 清单 3. DemoScript.js 示例println("Start script \r\n"); // Output the type of an object function printType(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 var newVar = { x: 1, y: { u: 2, v: 3 } } println("[JS] newVar: " + newVar); printType(newVar); println("End script \r\n"); 清单 4 是 ScriptDemo.java 示例的输出。值得注意的是 清单 4. ScriptDemo.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 原始变量和字符串转变成 Java 对象。其他的 JavaScript 对象则被包装成 Java 对象,这些对象能使用某种特定于引擎的内部 API,比如 有时,可能会只想使用那些标准的 API,因此 Java 代码和所执行脚本间的全部数据转换都应通过原始变量、字符串和 Java 对象(比如 bean)完成,这是因为在 JavaScript 代码内可以很容易地访问到它们的属性和方法。简言之,就是不要试图在 Java 代码内访问本地 JavaScript 对象,相反,应该在 JavaScript 代码内使用 Java 对象。 调用函数在之前的例子中,您已经看到了从 JavaScript 调用 Java 方法是可行的。现在您将会了解如何从 Java 代码调用 JavaScript 函数。首先,必须先执行包含想要调用的那个函数的脚本。然后,再将 InvDemo.java 示例(如清单 5 所示)执行一个名为 InvScript.js 的脚本,它包含 清单 5. InvDemo.java 示例package jsee.demo; import javax.script.*; import java.io.*; public class InvDemo { public static void main(String args[]) throws Exception { // Get the JavaScript engine ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("JavaScript"); // Run InvScript.js Reader scriptReader = new InputStreamReader( InvDemo.class.getResourceAsStream("InvScript.js")); try { engine.eval(scriptReader); } finally { scriptReader.close(); } // Invoke a JavaScript function if (engine instanceof Invocable) { 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 所示)包含 清单 6. InvScript.js 示例println("Start script \r\n"); function printType(obj) { if (obj.getClass) println(" Java object: " + obj.getClass().name); else println(" JS object: " + obj.toSource()); println(""); } function demoFunction(a, b) { println("[JS] a: " + a); printType(a); println("[JS] b: " + b); printType(b); var c = a + b; println("[JS] c: " + c); printType(c); return c; } println("End script \r\n"); InvDemo.java 的输出如清单 7 所示,注意到其中的数值参数均被转换成了 JavaScript 对象,并且由 清单 7. InvDemo.java 的输出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 请注意 编译脚本脚本在每次执行时都进行解析会浪费 CPU 资源。在多次执行相同的脚本时,若能编译脚本,就可以显著减少执行时间,而脚本编译所需要的方法可由另外一个可选接口
清单 8. CachedScript 类package jsee.cache; import javax.script.*; import java.io.*; import java.util.*; public class CachedScript { private Compilable scriptEngine; private File scriptFile; private CompiledScript compiledScript; private Date compiledDate; public CachedScript(Compilable scriptEngine, File scriptFile) { this.scriptEngine = scriptEngine; this.scriptFile = scriptFile; } public CompiledScript getCompiledScript() throws ScriptException, IOException { Date scriptDate = new Date(scriptFile.lastModified()); if (compiledDate == null || scriptDate.after(compiledDate)) { Reader reader = new FileReader(scriptFile); try { compiledScript = scriptEngine.compile(reader); compiledDate = scriptDate; } finally { reader.close(); } } return compiledScript; } }
默认地, 达到缓存的最大容量后, 通过联合使用 清单 9. ScriptCache 类package jsee.cache; import javax.script.*; import java.io.*; import java.util.*; public abstract class ScriptCache { public static final String ENGINE_NAME = "JavaScript"; private Compilable scriptEngine; private LinkedHashMap<String, CachedScript> cacheMap; public ScriptCache(final int maxCachedScripts) { ScriptEngineManager manager = new ScriptEngineManager(); scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME); cacheMap = new LinkedHashMap<String, CachedScript>( maxCachedScripts, 1, true) { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > maxCachedScripts; } }; } public abstract File getScriptFile(String key); public synchronized CompiledScript getScript(String key) throws ScriptException, IOException { CachedScript script = cacheMap.get(key); if (script == null) { script = new CachedScript(scriptEngine, getScriptFile(key)); cacheMap.put(key, script); } return script.getCompiledScript(); } public ScriptEngine getEngine() { return (ScriptEngine) scriptEngine; } } 下一节将使用 构建一个脚本运行程序在本节中,您将了解如何创建一个简单的 Java servlet 来实现 URL-脚本的映射以便能够从 Web 浏览器调用服务器端脚本。此外,servlet 还将会把几个 Java EE 对象公开为可在 JavaScript 代码内使用的变量。您还将了解如何使用脚本上下文来用单一一个 JavaScript 引擎运行多个并发的脚本。 初始化 servletservlet 类的名称是 清单 10. JSServlet 的 init() 方法package jsee.servlet; import javax.script.*; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import jsee.cache.*; public class JSServlet extends HttpServlet { private String cacheControlHeader; private String contentTypeHeader; private ScriptCache scriptCache; public void init() throws ServletException { ServletConfig config = getServletConfig(); cacheControlHeader = config.getInitParameter("Cache-Control"); contentTypeHeader = config.getInitParameter("Content-Type"); int maxCachedScripts = Integer.parseInt( config.getInitParameter("Max-Cached-Scripts")); scriptCache = new ScriptCache(maxCachedScripts) { public File getScriptFile(String uri) { return new File(getServletContext().getRealPath(uri)); } }; } ... } 清单 11 中包含一些 servlet 的参数,这些参数在 web.xml 文件内指定。 清单 11. web.xml 文件<web-app ...> <servlet> <servlet-name>JSServlet</servlet-name> <servlet-class>jsee.servlet.JSServlet</servlet-class> <init-param> <param-name>Cache-Control</param-name> <param-value>no-cache</param-value> </init-param> <init-param> <param-name>Content-Type</param-name> <param-value>text/plain</param-value> </init-param> <init-param> <param-name>Max-Cached-Scripts</param-name> <param-value>1000</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JSServlet</servlet-name> <url-pattern>*.jss</url-pattern> </servlet-mapping> </web-app> 从清单 11 可以看出,*.jss 模式被映射给此 servlet。这意味着 当 servlet 容器获得了 URL 以 .jss 结束的这个请求时,就会调用 使用脚本上下文脚本引擎的线程模型JSR-223 Scripting for the Java Platform(参见 参考资料)定义了三类脚本引擎:
脚本引擎的类型可通过 每个脚本引擎实例都具有一个默认的上下文,在其中,可以用 Mozilla 的 Rhino JavaScript 引擎是个多线程引擎(参见侧栏),允许执行共享相同上下文的并发线程。不过,在本例中,我们想要隔离这些引擎范围以及运行在不同线程内的那些脚本的输出,这意味着必须要针对每个 HTTP 请求创建一个新的 清单 12 给出了 此外, 表 1. 由 JSServlet 执行的脚本内的可用变量
清单 12. JSServlet 的 createScriptContext() 方法public class JSServlet extends HttpServlet { ... protected ScriptContext createScriptContext( HttpServletRequest request, HttpServletResponse response) throws IOException { ScriptContext scriptContext = new SimpleScriptContext(); scriptContext.setWriter(response.getWriter()); int scope = ScriptContext.ENGINE_SCOPE; scriptContext.setAttribute("config", getServletConfig(), scope); scriptContext.setAttribute("application", getServletContext(), scope); scriptContext.setAttribute("session", request.getSession(), scope); scriptContext.setAttribute("request", request, scope); scriptContext.setAttribute("response", response, scope); scriptContext.setAttribute("out", response.getWriter(), scope); scriptContext.setAttribute("factory", scriptCache.getEngine().getFactory(), scope); return scriptContext; } ... }
清单 13. JSServlet 的 runScript() 方法public class JSServlet extends HttpServlet { ... protected void runScript(String uri, ScriptContext scriptContext) throws ScriptException, IOException { scriptCache.getScript(uri).eval(scriptContext); } ... } 处理请求可以通过调用上述的 在同一个上下文中,可以连续运行多个脚本。例如,一个脚本可以定义一组变量和函数,而另一个脚本则可以使用之前在相同上下文内执行的脚本的变量和函数进行一些处理。 servlet 的 清单 14. JSServlet 的 handleRequest() 方法public class JSServlet extends HttpServlet { ... protected void handleRequest( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (cacheControlHeader != null) response.setHeader("Cache-Control", cacheControlHeader); if (contentTypeHeader != null) response.setContentType(contentTypeHeader); ScriptContext scriptContext = createScriptContext(request, response); try { runScript("/init.jss", scriptContext); String uri = request.getRequestURI(); uri = uri.substring(request.getContextPath().length()); try { runScript(uri, scriptContext); } catch (FileNotFoundException x) { response.sendError(404, request.getRequestURI()); } runScript("/finalize.jss", scriptContext); } catch (ScriptException x) { x.printStackTrace(response.getWriter()); throw new ServletException(x); } } ... }
清单 15. JSServletdo 的 Get() 和 doPost() 方法public class JSServlet extends HttpServlet { ... public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { handleRequest(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { handleRequest(request, response); } } 开发服务器端脚本本节包含了服务器端脚本的几个例子,向您展示了如何获得请求参数、访问 JavaBeans 的属性并生成 JSON 响应。 预处理和后处理在前一节中,曾提及在执行所请求的脚本之前, 清单 16. init.jss 脚本var debug = true; var debugStartTime = java.lang.System.nanoTime(); 之后,可以在 finalize.jss 内(参见清单 17)计算执行时间。该时间作为 JavaScript 注释打印以便 清单 17. finalize.jss 脚本var debugEndTime = java.lang.System.nanoTime(); if (debug) println("// Time: " + (debugEndTime - debugStartTime) + " ns"); 本系列后面的文章将向 init.jss 和 finalize.jss 添加更多的代码。 获得请求参数借助 清单 18. 在 init.jss 内获得请求参数。var param = new Object(); var paramValues = new Object(); function initParams() { var paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { var name = paramNames.nextElement(); param[name] = String(request.getParameter(name)); paramValues[name] = new Array(); var values = request.getParameterValues(name); for (var i = 0; i < values.length; i++) paramValues[name][i] = String(values[i]); } } initParams(); 假设您使用清单 19 所示的 URL 请求一个名为 ParamDemo.jss 的脚本。 清单 19. 请求一个脚本的 URL 示例http://localhost:8080/jsee/ParamDemo.jss?firstName=John&lastName=Smith 在 ParamDemo.jss(参见清单 20)内,用 清单 20. ParamDemo.jss 示例println("Hello " + param.firstName + " " + param.lastName); 访问 JavaBeanWeb 应用程序的
清单 21. init.jss 的 getBean() 和 setBean() 函数function getBean(scope, id) { return eval(scope).getAttribute(id); } function setBean(scope, id, bean) { if (!bean) bean = eval(id); return eval(scope).setAttribute(id, bean); } 现在,假设您想要在 清单 22. DemoBean.java 示例package jsee.demo; public class DemoBean { private int counter; public int getCounter() { return counter; } public void setCounter(int counter) { this.counter = counter; } } BeanDemo.jss 脚本(如清单 23 所示)用 清单 23. BeanDemo.jss 示例importPackage(Packages.jsee.demo); var bean = getBean("session", "demo"); if (!bean) { bean = new DemoBean(); setBean("session", "demo", bean); } bean.counter++; println(bean.counter); 如果所要导入的包是以 返回 JSON 响应清单 24 所示的示例会生成一个 JSON 响应,该响应会包含有关 JavaScript 引擎和此脚本 URI 的某些信息。此示例使用 JavaScript 语法来创建 清单 24. JsonDemo.jss 示例var json = { engine: { name: String(factory.engineName), version: String(factory.engineVersion), threading: String(factory.getParameter("THREADING")) }, language: { name: String(factory.languageName), version: String(factory.languageVersion) }, script: { uri: String(request.requestURI) } }; println(json.toSource()); 在本例中,从 清单 25. JsonDemo.jss 的输出({language:{name:"ECMAScript", version:"1.6"}, engine:{name:"Mozilla Rhino", threading:"MULTITHREADED", version:"1.6 release 2"}, script:{uri:"/jsee/JsonDemo.jss"}}) 结束语在本文中,您了解了如何使用 下载
|
|
来自: 风之飞雪 > 《Javascript》