JavaScript EE:在服務器端運行JavaScript文件

       本系列文章圍繞的主旨是瞭解如何在 Ajax 和 Java EE 應用程序內使用 javax.script API。如何將 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 頁面內的所謂的 “隱式對象”,比如 applicationsessionrequestresponse。本文中的大多數示例均包含可重用代碼,這樣一來,您可以在自己的應用程序中輕鬆地將 JavaScript 應用於服務器上。

 

使用 javax.script API

      本節給出了 javax.script API 的概覽,展示瞭如何執行腳本來訪問 Java 對象、如何從 Java 代碼調用 JavaScript 函數,以及如何爲所編譯的腳本實現緩存機制。

 

執行腳本

   javax.script API 十分簡單。可以先創建一個 ScriptEngineManager 實例,有了這個實例就能用下列方法中的任一個來獲得 ScriptEngine 對象(參見清單 1):

  • getEngineByName()
  • getEngineByExtension()
  • getEngineByMimeType()

 清單 1. 獲得一個 ScriptEngine 實例

import javax.script.*;
...
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
...
engine.eval(...);

 

      此外,還可以通過 getEngineFactories() 獲得可用腳本引擎的列表。目前,只有 JavaScript 引擎是與 JDK 6 捆綁的,不過 ScriptEngineManager 實現了一種發現機制,能發現支持 JSR-223 Scripting for the Java Platform 的第三方引擎(參見 參考資料)。

只需將腳本引擎的 JAR 文件放入 CLASSPATH 即可。

 

      獲得了 javax.script.ScriptEngine 實例後,就可以調用 eval() 來執行腳本了。也可以將 Java 對象作爲腳本變量導出,其間要將 Bindings 實例傳遞給 eval() 方法。

清單 2 所示的 ScriptDemo.java 示例導出兩個名爲 demoVarstrBuf 的變量、執行 DemoScript.js 腳本,然後讓這些變量輸出它們修改後的值。

 

清單 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 所示)包含一個 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");

// 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 示例的輸出。值得注意的是 demoVar 作爲 JavaScript String 導出,而 strBuf 的類型仍然是 java.lang.StringBuffer。原始變量和 Java 字符串均作爲本地 JavaScript 對象導出。任何其他的 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,比如 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;

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 所示)包含 demoFunction() 例程和之前的腳本示例所用的相同 printType() 函數。

 

清單 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 對象,並且由 demoFunction() 返回的值是作爲 Java 對象獲得的。這種轉換隻會針對原始變量和字符串進行。任何其他的對象在 JVM 和 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

 

      請注意 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;

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;
    }

}

 

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;

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 cacheMap;

    public ScriptCache(final int maxCachedScripts) {
        ScriptEngineManager manager = new ScriptEngineManager();
        scriptEngine = (Compilable) manager.getEngineByName(ENGINE_NAME);
        cacheMap = new LinkedHashMap(
                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;
    }
    
}

 

下一節將使用 ScriptCache 類、實現抽象的 getScriptFile() 方法並使用 getScript() 從緩存檢索所編譯的腳本。

 

構建一個腳本運行程序

在本節中,您將瞭解如何創建一個簡單的 Java servlet 來實現 URL-腳本的映射以便能夠從 Web 瀏覽器調用服務器端腳本。此外,servlet 還將會把幾個 Java EE 對象公開爲可在 JavaScript 代碼內使用的變量。您還將瞭解如何使用腳本上下文來用單一一個 JavaScript 引擎運行多個併發的腳本。

 

初始化 servlet

servlet 類的名稱是 JSServlet。其 init() 方法(參見清單 10)會獲得幾個配置參數並創建一個 ScriptCache 對象。servlet 的腳本緩存使用 getRealPath() 獲得與給定 URI 相映射的腳本文件的路徑。


清單 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 文件內指定。Cache-Control 頭與腳本緩存毫無關係。兩個頭都是由 servlet 返回的此 HTTP 響應的一部分。no-cache 值告知瀏覽器不要緩存此 servlet 的響應,該響應應被作爲 text/plain 對待。


清單 11. web.xml 文件

    
        JSServlet
        jsee.servlet.JSServlet
        
            Cache-Control
            no-cache
        
        
            Content-Type
            text/plain
        
        
            Max-Cached-Scripts
            1000
        
        1
    

    
        JSServlet
        *.jss
    

 

      從清單 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 {
    ...
    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;
    }
    ...
}

 

runScript() 方法(參見清單 13)從緩存獲得一個編譯後的腳本並調用 eval() 方法,將給定的腳本上下文作爲參數傳遞。


清單 13. JSServlet 的 runScript() 方法

public class JSServlet extends HttpServlet {
    ...
    protected void runScript(String uri, ScriptContext scriptContext)
            throws ScriptException, IOException {
        scriptCache.getScript(uri).eval(scriptContext);
    }
    ...
}

 

處理請求

可以通過調用上述的 runScript() 方法來執行 與此 HTTP 請求的 URL 相映射的那個腳本。不過,在實際的應用程序中,可能需要在運行腳本之前進行初始化並在腳本執行完後進行最後的清理工作。

 

在同一個上下文中,可以連續運行多個腳本。例如,一個腳本可以定義一組變量和函數,而另一個腳本則可以使用之前在相同上下文內執行的腳本的變量和函數進行一些處理。

 

servlet 的 handleRequest() 方法(如清單 14 所示)可設置這些 HTTP 報頭、運行 init.jss 腳本、從請求的 URI 中刪除此上下文路徑、執行具有所獲得的 URI 的腳本,然後運行另一個名爲 finalize.jss 的腳本。


清單 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);
        }
    }
    ...
}

 

JSServletdoGet()doPost() 方法(參見清單 15)用來調用 handleRequest()


清單 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 響應。

 

預處理和後處理

在前一節中,曾提及在執行所請求的腳本之前,JSServlet 會調用 init.jss(如清單 16 所示)。若想估量執行腳本所需時間,可以將開始時間存儲到一個變量內,如下所示。


清單 16. init.jss 腳本

var debug = true;
var debugStartTime = java.lang.System.nanoTime();

 

之後,可以在 finalize.jss 內(參見清單 17)計算執行時間。該時間作爲 JavaScript 註釋打印以便 JSServlet 能夠生成有效的 JSON 響應。


清單 17. finalize.jss 腳本

var debugEndTime = java.lang.System.nanoTime();
if (debug)
    println("// Time: " + (debugEndTime - debugStartTime) + " ns");

 

本系列後面的文章將向 init.jss 和 finalize.jss 添加更多的代碼。

 

獲得請求參數

藉助 JSServlet 調用的腳本可以通過 request.getParameter()request.getParameterValues() 訪問請求參數,這二者會返回 Java 對象。若想使語法更簡短或處理 JavaScript 對象而非 Java 字符串和數組,也不難,只需將下面這些代碼行加入到 init.jss(參見清單 18)。


清單 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)內,用 param.firstNameparam.lastName 可以得到這兩個請求參數。


清單 20. ParamDemo.jss 示例

println("Hello " + param.firstName + " " + param.lastName);

 

訪問 JavaBean

Web 應用程序的 applicationsessionrequest 範圍可存儲 bean 實例,可以分別使用 ServletContextHttpSessionHttpServletRequestgetAttribute()setAttribute() 來獲得或替代這些實例。

也可以使用 getBean()setBean() 函數(如清單 21 所示),但這兩個函數必須位於 init.jss 文件內以便任何腳本均可調用它們。scope 參數應是如下字符串中的一個:

  • "application"
  • "session"
  • "request"

清單 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);
}

現在,假設您想要在 session 範圍內保存 DemoBean(參見清單 22)的一個實例。


清單 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 所示)用 importPackage(Packages.jsee.demo) 導入了包含此 JavaBean 的那個包。之後,腳本試圖用 getBean()session 範圍獲得這個 bean 實例。

如果這個 bean 沒有找到,那麼 BeanDemo.jss 就會創建一個對象並利用 setBean() 將其放入 session 範圍。

最終,此腳本會進行增量處理並輸出這個 bean 的 counter 屬性。


清單 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);

 

如果所要導入的包是以 javajavaxorgeducomnet 開頭的,那麼無須在 importPackage() 使用 Packages 前綴。此外,還可以使用 importClass() 導入單個類。

 

返回 JSON 響應

清單 24 所示的示例會生成一個 JSON 響應,該響應會包含有關 JavaScript 引擎和此腳本 URI 的某些信息。此示例使用 JavaScript 語法來創建 json 對象,其源代碼則用 toSource() 方法以 String 形式獲得。


清單 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());

 

在本例中,從 factoryrequest 的屬性中檢索到的所有 Java 對象都必須轉變爲 JavaScript 對象,以便 toSource() 能夠正常工作。清單 25 包含了此腳本的輸出:


清單 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"}})

 

 

結束語

在本文中,您瞭解瞭如何使用 javax.script API 編譯和執行 JavaScript 文件。您還了解了如何基於java.util.LinkedHashMap 實現 LRU 緩存、如何將 Java 對象作爲腳本變量導出、如何實現 URL-腳本映射以及如何構建在服務器端執行 的 JavaScript 文件。請您繼續關注本系列的下一篇文章,在該文章中,您將瞭解如何用 Ajax 調用遠程 JavaScript 函數。

 

 

 

 

 

原載:http://www.ibm.com/developerworks/cn/web/wa-aj-javaee/?S_TACT=105AGX52&S_CMP=tec-csdn

 

作者:

Andrei Cioroianu 是 Devsphere 公司的創始人,該公司專門提供 Java EE 開發和 Web 2.0/Ajax 顧問服務。他自 1997 年就開始使用 Java 和 Web 技術,具有 10 多年解決複雜技術問題和管理商業產品的整個生命週期以及定製應用程序和開源框架的專業經驗。您可以通過 www.devsphere.com 上的聯繫列表與 Andrei 聯繫。

 

本文的示例應用程序:

發佈了86 篇原創文章 · 獲贊 0 · 訪問量 5422
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章