典型的 Ajax 應用程序在客戶端一般都使用 JavaScript,而在服務器端常常使用另外一種語言,比如 Java。因此,開發(fā)人員必須將其中一些例程實現兩次,一次用于在 Web 瀏覽器使用 JavaScript,另一次用于在服務器使用另外一種語言。這種雙重編碼問題實際上可以通過將 JavaScript 和服務器端的 Java 代碼結合起來加以避免,而對腳本語言的完整支持可以通過 在本系列的第一篇文章中,將使用一個簡單的腳本運行程序來在一個 Jave EE 應用程序內執(zhí)行 JavaScript 文件。這些腳本將能訪問被用在 JSP 頁面內的所謂的 “隱式對象”,比如 使用 javax.script API本節(jié)給出了 執(zhí)行腳本
清單 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 運行該腳本后,此引擎就會接受所有變量(包括新變量)并執(zhí)行反轉變換,將 JavaScript 原始變量和字符串轉變成 Java 對象。其他的 JavaScript 對象則被包裝成 Java 對象,這些對象能使用某種特定于引擎的內部 API,比如 有時,可能會只想使用那些標準的 API,因此 Java 代碼和所執(zhí)行腳本間的全部數據轉換都應通過原始變量、字符串和 Java 對象(比如 bean)完成,這是因為在 JavaScript 代碼內可以很容易地訪問到它們的屬性和方法。簡言之,就是不要試圖在 Java 代碼內訪問本地 JavaScript 對象,相反,應該在 JavaScript 代碼內使用 Java 對象。 調用函數在之前的例子中,您已經看到了從 JavaScript 調用 Java 方法是可行的?,F在您將會了解如何從 Java 代碼調用 JavaScript 函數。首先,必須先執(zhí)行包含想要調用的那個函數的腳本。然后,再將 InvDemo.java 示例(如清單 5 所示)執(zhí)行一個名為 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 請注意 編譯腳本腳本在每次執(zhí)行時都進行解析會浪費 CPU 資源。在多次執(zhí)行相同的腳本時,若能編譯腳本,就可以顯著減少執(zhí)行時間,而腳本編譯所需要的方法可由另外一個可選接口
清單 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; } }
默認地, 達到緩存的最大容量后, 通過聯(lián)合使用 清單 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; } } 下一節(jié)將使用 構建一個腳本運行程序在本節(jié)中,您將了解如何創(chuàng)建一個簡單的 Java servlet 來實現 URL-腳本的映射以便能夠從 Web 瀏覽器調用服務器端腳本。此外,servlet 還將會把幾個 Java EE 對象公開為可在 JavaScript 代碼內使用的變量。您還將了解如何使用腳本上下文來用單一一個 JavaScript 引擎運行多個并發(fā)的腳本。 初始化 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 引擎是個多線程引擎(參見側欄),允許執(zhí)行共享相同上下文的并發(fā)線程。不過,在本例中,我們想要隔離這些引擎范圍以及運行在不同線程內的那些腳本的輸出,這意味著必須要針對每個 HTTP 請求創(chuàng)建一個新的 清單 12 給出了 此外, 表 1. 由 JSServlet 執(zhí)行的腳本內的可用變量
清單 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); } ... } 處理請求可以通過調用上述的 在同一個上下文中,可以連續(xù)運行多個腳本。例如,一個腳本可以定義一組變量和函數,而另一個腳本則可以使用之前在相同上下文內執(zhí)行的腳本的變量和函數進行一些處理。 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); } } 開發(fā)服務器端腳本本節(jié)包含了服務器端腳本的幾個例子,向您展示了如何獲得請求參數、訪問 JavaBeans 的屬性并生成 JSON 響應。 預處理和后處理在前一節(jié)中,曾提及在執(zhí)行所請求的腳本之前, 清單 16. init.jss 腳本var debug = true; var debugStartTime = java.lang.System.nanoTime(); 之后,可以在 finalize.jss 內(參見清單 17)計算執(zhí)行時間。該時間作為 JavaScript 注釋打印以便 清單 17. finalize.jss 腳本var debugEndTime = java.lang.System.nanoTime(); if (debug) println("http:// 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 語法來創(chuàng)建 清單 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》