使用 AJAX 和 Spring 整合進行 Java 到 JavaScript 編譯

工程 | Ben Alex | 2007 年 1 月 22 日 | ...

一段時間以來,我一直對以客戶端為中心的基於 Web 的使用者介面感興趣。這些第四代框架的特點是其基於元件、事件驅動的程式設計模型,並專注於完全駐留在客戶端的表示邏輯。以這種方式針對 Web 瀏覽器通常需要使用 JavaScriptFlash,這本身就帶來了一些獨特的挑戰。

如果我們能夠使用 Java 程式設計,並自動生成 JavaScript 或 Flash 執行時模組,那麼許多這些挑戰就可以得到解決。目前,有兩個知名的產品可以實現這一點,分別是 Google Web Toolkit (GWT) 和 Open Laszlo。兩者都根據 OSI 批准的許可證 提供,並擁有活躍的社群,但各自都有其獨特的複雜性。一個需要考慮的方面是,它們在多大程度上實現了提供一個面向 Web 瀏覽器部署的透明 Java 開發環境的目標。這個考慮有幾個方面,包括 IDE 支援、除錯整合、反射能力、執行時小部件繫結等等。所有這些都是使用傳統的 Java 技術(如 SwingStandard Widget Toolkit (SWT))開發富客戶端時的常規考慮因素。

這篇博文的目的不是評測 GWT 或 Open Laszlo。相反,我想探討一個名為 Java2Script Pacemaker (J2S) 的開源 Java 轉 JavaScript 編譯器,並介紹一個初步的 Spring 整合。這個有趣的專案不太為人所知,但它以一種令人鼓舞的方式解決了透明的 Java 轉 JavaScript 開發問題。J2S 附帶一個增量編譯器,幾乎完整的 `java.lang`、`java.io` 和 `java.util` 包的 JavaScript 版本,一個 Junit 的 JavaScript 版本,一個 Eclipse SWT 的 JavaScript 實現,以及一個 AJAX 庫。更重要的是,J2S 可以將任何現有的 Java 程式碼轉換為 JavaScript,前提是原始碼和依賴項也能夠以類似的方式轉換為 JavaScript。

從技術角度來看,J2S 目前在幾個大的方面與 GWT 不同。首先是其編譯器技術,它構建在 Eclipse 的 抽象語法樹 (AST) 之上,因此需要 Eclipse。然而,Eclipse JDT Core 支援無頭模式,因此從 Ant 外掛或 Maven mojo 執行 J2S 編譯不會很難。第二個區別是 J2S 提供了全面的執行時反射和小部件繫結功能。GWT 傾向於在編譯時進行 JavaScript 最佳化,犧牲了這些執行時服務。另一方面,J2S 認為 摩爾定律、改進的瀏覽器 JavaScript 直譯器以及 JNI 類似的 JavaScript 最佳化共同提供了足夠的效能空間,同時仍然享受更完整的 JRE 模擬和其他執行時服務。

也許最大的技術差異與使用者介面方法有關。GWT 提供了自己的類似 Swing 的 API,該 API 是為 Web 瀏覽器整合而設計的。另一方面,J2S 旨在提供 SWT 的實現。J2S 的方法有很多顯而易見的優勢

  • 您的應用程式可以針對富客戶端(在 JVM 中)和 Web UI(在 Web 瀏覽器中),只需少量甚至無需重寫程式碼;
  • 開發和除錯 J2S 應用程式與開發和除錯普通的 SWT 應用程式基本相同,這大大降低了新開發人員的學習曲線;
  • 組織有機會吸引具有 SWT 技能的人員;
  • SWT 有大量的 文獻社群 資源 可用;
  • SWT 是一個穩定且經過生產驗證的 API;以及
  • 可以使用 開源商業 工具來幫助您構建 SWT 應用程式。
不過,J2S 確實存在一些需要考慮的侷限性。這些包括
  • 透過 Internet 下載速度可能很慢。考慮到大多數 JEE 應用程式都用於內網部署,我不確定這是否是一個主要障礙。此外,JavaScript 壓縮和客戶端快取可以最大限度地減少這些延遲。
  • 執行速度可能很慢,儘管程式碼下載後我並未覺得太糟糕。有各種最佳化可用以提高速度。此外,還有為有能力的使用者提供 JVM 託管的 SWT 版本使用者介面的選項,由於共享的 SWT 程式碼庫,這應不會增加太多額外的開發成本。
  • JFace 目前不受支援,並且完整的 JRE 未被模擬。一個值得注意的例外是 `java.io.File`。如果您的應用程式依賴於此類,它將無法原生編譯為 JavaScript。相反,您需要使用 J2S 的 `@j2sNative` 功能將相關類編譯為 JavaScript。我還遇到了一些 SWT 實現的微小問題,但都不算太嚴重。
  • J2S 的 社群相對較小,該框架迄今為止尚未得到廣泛應用。不過,每個開源專案都從小處開始,需要有機會成長。
  • Eclipse 以外 IDE 的使用者將無法使用當前形式的 J2S。如前所述,AST 編譯模型允許 Eclipse JDT 支援無頭模式,因此這並不是一個主要問題。
Spring 社群也會理所當然地想知道,“它能與 Spring 一起工作嗎?”。答案實際上取決於您想達到什麼目的。如果您打算構建標準的 SWT 或 Swing 應用程式,通常會在使用者介面層使用這些技術來訪問遠端服務層。因此,您主要的 Spring 整合問題在於是否存在合適的遠端機制。Spring 為 Java 到 Java 的遠端呼叫提供了廣泛的成熟 基礎設施,大多數專案選擇同步的 HttpInvokerRMISOAP

透過生成基於 JavaScript 的客戶端,J2S 顯然需要某種形式的 Java 到 JavaScript 遠端呼叫。Java 到 JavaScript 的遠端呼叫實現採用非同步方法是很常見的,這意味著在遠端呼叫後執行會立即繼續,並且在接收到呼叫結果後有一個單獨的回撥來處理。Java 到 JavaScript 遠端呼叫的兩種主要方法是 DWRJSON-RPC,儘管 GWT 和 J2S 都提供了自己獨立的遠端呼叫方法。GWT 和 J2S 的方法都不能開箱即用地提供 Spring 整合,儘管 Spring 的靈活架構使得這樣做相當容易(正如我下面將在 J2S 的情況下向您展示的那樣)。

在我們檢視 Spring 實現之前,讓我們回顧一下 J2S AJAX 遠端呼叫協議是如何工作的。J2S 為每個潛在的遠端呼叫採用一種準 命令模式。`SimpleRPCRunnable` 超類提供了 JavaScript 到 Java 和 Java 到 JavaScript 的序列化,子類指示遠端 URL、要序列化的欄位以及要在遠端執行的邏輯。


public class LZ77JSSimpleRPCRunnable extends SimpleRPCRunnable {

private transient SomeServicesLayer servicesLayer; // setter omitted
public String jsContent;
public String result;

public String getHttpURL() {
return "https://:8080/echotest/simplerpc";
}

public void ajaxRun() {
result = servicesLayer.computeTheAnswer(jsContent);
jsContent = null;
}
}

欄位宣告很重要。每個公共的非瞬態欄位都會被 `SimpleRPCRunnable` 序列化。`getHttpURL()` 指定了 J2S servlet 的 URL。任何 J2S 命令都可以使用相同的 URL,使其成為您應用程式的 J2S 前端控制器。`ajaxRun()` 方法包含將在伺服器端執行的邏輯。在本例中,我們的 `ajaxRun()` 方法正在訪問本地(伺服器端)的 Spring bean。注意 `servicesLayer` 欄位宣告為瞬態,這意味著 `SimpleRPCRunnable` 不會序列化它。取而代之的是,Spring IoC 容器會將 `SomeServicesLayer` 例項依賴注入到我們的伺服器端命令物件中。因此,`servicesLayer` 在 J2S 客戶端端始終為 null。為了讓客戶端非同步呼叫該命令,他們會使用類似以下的程式碼:


SimpleRPCSWTRequest.swtRequest(new LZ77JSSimpleRPCRunnable() {

public void ajaxIn() {
jsContent = sourceText.getText();
}

public void ajaxOut() {
resultText.setText(result);
}
});

如所示,`ajaxIn()` 方法用於在客戶端上將公共欄位設定為可接受的值。`ajaxOut()` 方法是非同步回撥處理程式,這意味著一旦命令物件從伺服器返回並反序列化後,就會執行它。在本例中,命令正在更新一個 UI 小部件。下面的螢幕截圖顯示了在 JVM 託管的 SWT 應用程式中執行該命令的結果。

Figure 1
下一個螢幕截圖顯示了在 Firefox 託管的 SWT 應用程式中執行同一命令的結果。在這些執行時目標之間,無需更改任何程式碼或遠端配置,這說明了 J2S 方法的靈活性和吸引力。
Figure 2
`SimpleRPCSWTRequest` 還提供了兩個靜態方法來宣告呼叫是否應該實際透過網路發生。`SimpleRPCSWTRequest.switchToLocalJavaThreadMode()` 將導致 `ajaxRun()` 方法在本地呼叫,這在執行 JVM 託管的 SWT 應用程式時可能很合適。要使呼叫跨網路序列化(並因此 `ajaxRun()` 在伺服器端執行),只需呼叫 `SimpleRPCSWTRequest.switchToAJAXMode()`。此模式與瀏覽器和 JVM 目標平臺都相容,因此使用 J2S 構建多目標使用者介面不需要為 JVM 目標使用額外的遠端協議(例如 HttpInvoker 或 RMI)。

在伺服器端,我們沒有使用普通的 J2S `SimpleRPCHttpServlet`。相反,我們正在使用一個名為 `SpringRpcHttpServlet` 的新類(該類與本文中引用的所有其他程式碼一起,作為 ZIP 附件 提供)。`SpringRpcHttpServlet` 的操作與普通的 `SimpleRPCHttpServlet` 相同,除了它從 Spring 應用程式上下文中獲取伺服器端命令物件。程式碼文件齊全,如果您有興趣詳細瞭解它是如何工作的,請檢視 ZIP 附件。它基本上允許您在 Spring 應用程式上下文檔案中定義命令及其依賴項。

如果您的應用程式需要其他命令,只需建立一個 `SimpleRPCRunnable` 子類,然後將其新增到您的應用程式上下文中即可。關注我工作的人可能會對 ROO 感興趣,他們會很高興聽到我打算提供 J2S 遠端整合,讓您無需編寫命令物件或透過 `SimpleRPCSWTRequest` 呼叫。

總之,J2S 為需要 JavaScript 編譯或 SWT 的 Web 瀏覽器實現的專案提供了有吸引力的優勢。它還可以成功地與 Spring 後端互操作。J2S 刻意選擇利用現有的成熟技術,如 AST 和 SWT,使其成為重用現有程式碼和開發人員技能的一個很好的例子,從而降低了採用門檻和重大 API 更改的可能性。如果您認為自己是早期採用者、SWT 的擁護者,或者需要一個以客戶端為中心、基於 Web 的使用者介面,並且構建在成熟的 SWT UI 框架之上,那麼 J2S 絕對值得您進一步關注。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有