2010-02-05

徹底瞭解 GWT Part 1:JSNI

原文:http://googlewebtoolkit.blogspot.com/2008/07/getting-to-really-know-gwt-part-1-jsni.html

技術校正、審閱:tkcn

[前面略過一段很口語、很難翻譯、但是不太重要的一段 XD]

徹底瞭解 GWT,第一步:JSNI
如果你是 GWT 新手,你可能覺得奇怪:到底有什麼好激動的?GWT 跟其他 framework 有什麼不同?GWT 是一個基礎技術、串起許多工具,而不只是特定 application 的 framework。因此,雖然 GWT「有」很多 library,覺得有用的話,要用多用少都可以。不喜歡 GWT 的 UI?你可以用 DOM 建立自己的 UI。不想用 RPC 而想要用 JSON?那也沒問題。事實上,要用 GWT 重頭開始打造自己的  framework 是絕對沒問題的,也能保留所有 GWT debug 跟 compile 方面的優點。

所以,用一個宏觀的角度來思考:為什麼你要考慮使用 GWT 來開發下一個大的 web application 呢?
  • 相較於傳統 server-centric 的方式,如果 AJAX 設計得好,因為本身的特性,在許多種類的 application 上會提供更好的使用經驗。你都已經在讀這個 blog 了,可能早就知道啦。
  • GWT compiler 產生的 JavaScript 碼通常會比你自己撰寫的程式碼還要小、還要快。
  • 用 GWT 開發可以讓你更有產能、而且產生的 JavaScript 也更加可靠。
  • You get the opportunity to say "Gwit" a lot.

我們擔心你是那種容易被一些主張或是行銷說詞給影響了,所以我們在接下來幾篇 blog 文章中,會看到 GWT 如何實現性能的提昇、以及如何讓 JavaScript 使用起來更容易。

在深入技術細節之前,還有一件事。我們有時會問兩個跟 GWT 本質有關的問題:
  1. 為什麼 GWT 以 Java 語言和工具為核心?
  2. 跟手寫的 JavaScript 比起來,GWT 是不是比較多限制、也比較 heavyweight?

先回答第一個問題,請瞭解我們的目標跟滿腔熱血,是為了徹底改善網頁使用者的使用者經驗,這表示 GWT 產生的 JavaScript 必須有最好的效能跟可靠度。為了做到這一點,我們自然想加入一堆程式碼的最佳化,並且盡可能的提早抓出 bug。這兩個目標都容易在 Java 的 static type、以及既有的 Java IDE 來達成。這是為什麼我們冷靜地選擇了 Java 技術作為 GWT 的核心。就是這樣—沒啥語言聖戰的梗。

再來回答第二個問題,有些開發人員在第一次聽到 GWT 時,會假設它是一個 abstraction 的「Walled Garden」,讓你永遠只能用 Java 寫程式而不讓你使用或是整合手寫的 JavaScript。這實在錯的很離譜......

GWT 中的 JavaScript Native Interface(JSNI)
你可以輕鬆地把手寫的 JavaScript 直接嵌入 GWT 程式碼當中。反正最後都會變成 JavaScript,那為甚麼不提供 GWT 的開發人員一個有用的方法,讓他們能夠混合這兩個東西?這方法就叫做 JSNI 啦。它的名字跟 JNI(Java Native Interface)很像,因為他們的基本想法也一樣:把一個 Java method 宣告成「native」然後用另一個語言來實做內容。在 JSNI 當中,就是用 JavaScript 來實做內容。

用 JavaScript 寫 Java Method
JSNI 在創造功能最底層的 abstraction 是非常有用的,它很自然地使用 JavaScript、而不是 Java 語法。例如 Regular Expression 在 JavaScript 是相當簡潔的,所以你可以透過 JSNI 在 GWT 程式碼當中直接使用它們。假設你想把某人的名字(Ps Monkey)轉過來,讓姓在後面、名字在前面(Monkey, Ps)(當然,這在 I18N 當中會是一場惡夢)。你可以建立一個很簡短的 JSNI method 來做到:
// Java method declaration...
native String flipName(String name) /*-{
  // ...implemented with JavaScript
  var re = /(\w+)\s(\w+)/;
  return name.replace(re, '$2, $1');
}-*/;
請注意,method 的內容整個被特殊標記「/*-」、「-*/」給包圍起來,變成一段 Java 的註解。

在 JSNI 中呼叫 Java method
你也可以反過來,在 JavaScript 呼叫一個 Java 的 method。假設我們修改上面的例子,改成這樣:
package org.example.foo;
public class Flipper {
  public native void flipName(String name) /*-{
    var re = /(\w+)\s(\w+)/;
    var s = name.replace(re, '$2, $1');
    this.@org.example.foo.Flipper::onFlip(Ljava/lang/String;)(s);
  }-*/;

  private void onFlip(String flippedName) {
    // do something useful with the flipped name
  }
}

用 JSNI 存取外部的 JavaScript 程式碼
當然,你可以在 GWT module 存取任何外部的 JavaScript 程式碼。舉例來說,如果你的 HTML 頁面看起來像這樣:
<html>
  <head>
    <SCRIPT>
    function sayHello(name) {
        alert("Hello from JavaScript, " + name);
    }
    </script>    
    <-- Include the GWT module called "Spiffy" -->
    <script src="org.example.yourcode.Spiffy.nocache.js"></script>
  </head>
...
在 Java 程式碼,您可以用 JSNI 去呼叫 JavaScript 的 sayHello() function:
// A Java method using JSNI
native void sayHelloInJava(String name) /*-{
  $wnd.sayHello(name); // $wnd is a JSNI synonym for 'window'
}-*/;
GWT compiler 把外部的 method 呼叫給內嵌進來,所以在 Java 當中呼叫 sayHelloInJava() 的效率並不會比直接在 JavaScript 當中呼叫來的差。

用 GWT 建立 JavaScript library
你甚至可以用 GWT 創造一個 JavaScript 可以呼叫的 library。這是一個非常棒的技巧:
package org.example.yourcode.format.client;
public class DateFormatterLib implements EntryPoint {

  // Expose the following method into JavaScript.
  private static String formatAsCurrency(double x) {
    return NumberFormat.getCurrencyFormat().format(x);
  }
  // Set up the JS-callable signature as a global JS function.
  private native void publish() /*-{
  $ wnd.formatAsCurrency =
    @org.example.yourcode.format.client.DateFormatterLib::formatAsCurrency(D);
}-*/;

  // Auto-publish the method into JS when the GWT module loads.
  public void onModuleLoad() {
    publish();
  }
}

接著,你可以在任何 HTML 或是 JavaScript library 當中存取這個 GWT 做出來的功能:
<html>
  <head>
  
    <-- Include the GWT module that publishes the JS API -->
    <script src="org.example.yourcode.FormatLib.nocache.js"></script>

    <-- Write some JS that uses that GWT code -->
    <SCRIPT>
    function doStuff() {
      alert(formatAsCurrency(1530281));
    }
    </script>
  </head>
...

Ray Cromwell(最有名的是 GWT Extreme!)在他的 GWT Exporter 淋漓盡致地採用了上述的 JavaScript 發佈技術。它用 GWT compile 期的 code 產生器來自動創造所有發佈出的程式碼。(「compile 階段的程式碼產生器」聽起來很酷吧?如果你也這樣覺得,那請期待這個系列的後續文章。)

摻在一起作撒尿牛丸
用 JSNI,你可以用你想要的方式,自由地混和手工撰寫的 JavaScript、外部的 JavaScript library、以及 Java 程式碼。這就是我們說 GWT 並不是一個 abstraction 的「walled garden」,因為你可以逐步地將 GWT 融入到既有的 web application 當中。更棒的是,你可以用你喜歡的 Java debugger 來 debug 所有 Java 程式碼。

想要了解更多的話,請看 GWT Developer Guide 的 JavaScript Native Interface。或著,讓 GWT compiler 的設計師 Scott Blum 解釋給你聽也不錯喔。