小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

JavaScript EE,第 1 部分: 在服務器端運行 JavaScript 文件

 風之飛雪 2014-03-19

典型的 Ajax 應用程序在客戶端一般都使用 JavaScript,而在服務器端常常使用另外一種語言,比如 Java。因此,開發(fā)人員必須將其中一些例程實現兩次,一次用于在 Web 瀏覽器使用 JavaScript,另一次用于在服務器使用另外一種語言。這種雙重編碼問題實際上可以通過將 JavaScript 和服務器端的 Java 代碼結合起來加以避免,而對腳本語言的完整支持可以通過 javax.script API 獲得。此外,Java SE Development Kit (JDK) 6 已經包含了 Mozilla 的 Rhino JavaScript 引擎,這意味著您將無需進行任何設置。

在本系列的第一篇文章中,將使用一個簡單的腳本運行程序來在一個 Jave EE 應用程序內執(zhí)行 JavaScript 文件。這些腳本將能訪問被用在 JSP 頁面內的所謂的 “隱式對象”,比如 application、sessionrequestresponse。本文中的大多數示例均包含可重用代碼,這樣一來,您可以在自己的應用程序中輕松地將 JavaScript 應用于服務器上。

使用 javax.script API

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

執(zhí)行腳本

javax.script API 十分簡單??梢韵葎?chuàng)建一個 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 實現了一種發(fā)現機制,能發(fā)現支持 JSR-223 Scripting for the Java Platform 的第三方引擎(參見 參考資料)。只需將腳本引擎的 JAR 文件放入 CLASSPATH 即可。

獲得了 javax.script.ScriptEngine 實例后,就可以調用 eval() 來執(zhí)行腳本了。也可以將 Java 對象作為腳本變量導出,其間要將 Bindings 實例傳遞給 eval() 方法。清單 2 所示的 ScriptDemo.java 示例導出兩個名為 demoVarstrBuf 的變量、執(zhí)行 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

運行該腳本后,此引擎就會接受所有變量(包括新變量)并執(zhí)行反轉變換,將 JavaScript 原始變量和字符串轉變成 Java 對象。其他的 JavaScript 對象則被包裝成 Java 對象,這些對象能使用某種特定于引擎的內部 API,比如 sun.org.mozilla.javascript.internal.NativeObject

有時,可能會只想使用那些標準的 API,因此 Java 代碼和所執(zhí)行腳本間的全部數據轉換都應通過原始變量、字符串和 Java 對象(比如 bean)完成,這是因為在 JavaScript 代碼內可以很容易地訪問到它們的屬性和方法。簡言之,就是不要試圖在 Java 代碼內訪問本地 JavaScript 對象,相反,應該在 JavaScript 代碼內使用 Java 對象。

調用函數

在之前的例子中,您已經看到了從 JavaScript 調用 Java 方法是可行的?,F在您將會了解如何從 Java 代碼調用 JavaScript 函數。首先,必須先執(zhí)行包含想要調用的那個函數的腳本。然后,再將 ScriptEngine 實例強制轉換為 javax.script.Invocable,后者還提供了 invokeFunction()invokeMethod()。如果腳本實現了 Java 接口的全部方法,那么也可以使用 getInterface() 獲得一個 Java 對象,該對象的方法用此腳本語言編碼。

InvDemo.java 示例(如清單 5 所示)執(zhí)行一個名為 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 引擎提供對該接口的支持。

編譯腳本

腳本在每次執(zhí)行時都進行解析會浪費 CPU 資源。在多次執(zhí)行相同的腳本時,若能編譯腳本,就可以顯著減少執(zhí)行時間,而腳本編譯所需要的方法可由另外一個可選接口 javax.script.Compilable 提供,JDK 6 所帶的 JavaScript 引擎亦支持該接口。

CachedScript 類(參見清單 8)接受一個腳本文件并只有當源代碼有修改時才會進行重編譯。getCompiledScript() 方法會調用此腳本引擎的 compile(),進而返回 javax.script.CompiledScript 對象,該對象的 eval() 方法會執(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;
    }

}

ScriptCache 類(參見清單 9)使用 java.util.LinkedHashMap 對象為所編譯的腳本實現存儲庫。map 的初始容量被設為所緩存腳本的最大數量并且加載系數是 1。這兩個參數就確保了該 cacheMap 不需要重新處理。

默認地,LinkedHashMap 類會使用條目的插入順序。若不想使用默認順序,LinkedHashMap() 構造函數的第三個參數必須是 true 以便使用條目的訪問順序。

達到緩存的最大容量后,removeEldestEntry() 方法就會開始返回 true,以便當每次向此緩存添加一個新的編譯了的腳本時,一個條目都會自動從 cacheMap 刪除。

通過聯(lián)合使用 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<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é)將使用 ScriptCache 類、實現抽象的 getScriptFile() 方法并使用 getScript() 從緩存檢索所編譯的腳本。

構建一個腳本運行程序

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

初始化 servlet

servlet 類的名稱是 JSServlet。其 init() 方法(參見清單 10)會獲得幾個配置參數并創(chuàng)建一個 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 文件
<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。這意味著 JSServlet 將會處理 URL 以 .jss 擴展名結束的所有請求。當用戶在 Web 瀏覽器內輸入這樣的一個 URL 或是單擊了一個 .jss 鏈接時,瀏覽器就會發(fā)送此 HTTP 請求給 Web 服務器(例如, Apache),而此服務器應該被配置成將該請求分派給 servlet 容器(比如,Tomcat)。如果 servlet 容器也充當 Web 服務器,就無需額外的配置。

當 servlet 容器獲得了 URL 以 .jss 結束的這個請求時,就會調用 service() 方法,該方法是由 JSServlet 繼承自 javax.servlet.http.HttpServlet 的。此方法再進而調用 doGet()doPost(),最終調用哪一個取決于此請求的 HTTP 方法。兩個方法都可由 JSServlet 覆蓋,這一點在本節(jié)稍后的部分還會看到。

使用腳本上下文

腳本引擎的線程模型

JSR-223 Scripting for the Java Platform(參見 參考資料)定義了三類腳本引擎:

  • 能執(zhí)行并發(fā)腳本的多線程引擎,可以修改由其他線程看到的變量
  • 線程隔離引擎,也是多線程的,但每個線程都具有其自身的引擎范圍來保存變量
  • 無狀態(tài)引擎,作為線程隔離引擎定義,但引擎范圍在任何腳本執(zhí)行后均保持不變

腳本引擎的類型可通過 engine.getFactory().getParameter("THREADING") 獲得,而返回的結果可能會是 "MULTITHREADED"、"THREAD-ISOLATED""STATELESS"。

每個腳本引擎實例都具有一個默認的上下文,在其中,可以用 put() 方法存儲變量,而所執(zhí)行的腳本的輸出則被默認定向到 System.out。在服務器環(huán)境內,常希望運行具有各自上下文的并發(fā)腳本。javax.script API 能滿足這個需求,它能提供 ScriptContext 接口和 SimpleScriptContext 實現。

Mozilla 的 Rhino JavaScript 引擎是個多線程引擎(參見側欄),允許執(zhí)行共享相同上下文的并發(fā)線程。不過,在本例中,我們想要隔離這些引擎范圍以及運行在不同線程內的那些腳本的輸出,這意味著必須要針對每個 HTTP 請求創(chuàng)建一個新的 ScriptContext 實例。

清單 12 給出了 JSServlet 類的 createScriptContext() 方法。此方法設置了上下文的 writer 屬性以便在腳本執(zhí)行時將腳本的輸出發(fā)送給 response 對象的編寫者。這意味著傳遞給腳本內的 print()println() 的任何東西都將會包含在此 servlet 的響應內。

此外,createScriptContext() 還通過腳本上下文的 setAttribute() 方法定義了如下的腳本變量:

表 1. 由 JSServlet 執(zhí)行的腳本內的可用變量
腳本變量 描述
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() 方法來執(zhí)行 與此 HTTP 請求的 URL 相映射的那個腳本。不過,在實際的應用程序中,可能需要在運行腳本之前進行初始化并在腳本執(zhí)行完后進行最后的清理工作。

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

servlet 的 handleRequest() 方法(如清單 14 所示)可設置這些 HTTP 報頭、運行 init.jss 腳本、從請求的 URI 中刪除此上下文路徑、執(zhí)行具有所獲得的 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);
    }

}

開發(fā)服務器端腳本

本節(jié)包含了服務器端腳本的幾個例子,向您展示了如何獲得請求參數、訪問 JavaBeans 的屬性并生成 JSON 響應。

預處理和后處理

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

清單 16. init.jss 腳本
var debug = true;
var debugStartTime = java.lang.System.nanoTime();

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

清單 17. finalize.jss 腳本
var debugEndTime = java.lang.System.nanoTime();
if (debug)
    println("http:// 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 應用程序的 application、sessionrequest 范圍可存儲 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 就會創(chuàng)建一個對象并利用 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);

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

返回 JSON 響應

清單 24 所示的示例會生成一個 JSON 響應,該響應會包含有關 JavaScript 引擎和此腳本 URI 的某些信息。此示例使用 JavaScript 語法來創(chuàng)建 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 對象都必須轉變?yōu)?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 編譯和執(zhí)行 JavaScript 文件。您還了解了如何基于 java.util.LinkedHashMap 實現 LRU 緩存、如何將 Java 對象作為腳本變量導出、如何實現 URL-腳本映射以及如何構建在服務器端執(zhí)行 的 JavaScript 文件。請您繼續(xù)關注本系列的下一篇文章,在該文章中,您將了解如何用 Ajax 調用遠程 JavaScript 函數。

回頁首

下載

描述 名字 大小
本文的示例應用程序 jsee_part1_src.zip 25KB

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多