2013年12月29日 星期日

GWT 的 AutoBean

AutoBean 是目前打算拿來在 GWT 中處理 JSON 的工具。 不過在講正事之前,先扯兩段雜談 [毆飛]

雜談 1:這樣也可以?

要不是要弄 web API,其實也不會想碰 JSON, GWT RPC 好好的幹麼弄什麼 JSON [遠目]。 不過 server side 要處理 JSON 其實有 GSON, 正常 encode / decode 真的都還蠻簡單的, 簡單到不知道能介紹什麼 XD。 是說 LaPass(ptt.cc)因為有一個我覺得有點詭異的需求, 結果挖出了 TypeAdapterFactory 的用法, 看起來真的很乾淨很炫(炫到都快看不懂了 [遮臉]), 只能說好 library 如當是也。

理所當然的,會想看看有沒有 GWT 版的 GSON, 結果看到 bGwtGson 這玩意差點噴出來。 因為他的作法是用 GWT RPC 把東西丟到 server side, 這樣 server side 就有 GSON 可以用了… 揪咪…

我都不知道該說牛逼還是坑爹,這世界果然很廣大阿 [握拳]

喔對,順帶一提,GSON 在 AppEngine 上也可以使用。

雜談 2:謎樣裏技?

我搞不太懂 AutoBean 在 GWT 當中扮演什麼樣的角色? 彷彿還蠻多人在用的(因為大家都炸同樣的問題… Orz), 但是官方指南似乎沒有這個東西(JavaDoc 當然還是有), 教學文件只有出現在 google code 的 wiki 上, 所以這是裏技嗎?

我比較怕這是即將被拋棄的裏技 Orz

畢竟要在 GWT 裡頭處理 JSON 並不是太麻煩。 官方指南建議的 JSNI / Overlay Types 寫起來堪稱簡單直覺, 尤其是跟 AutoBean 相比的話 [死]。 再不然 GWT-Jackson 好像也是種選擇?

剛好兩者的風格我都不愛 [淚目]

最大的哏在於 AutoBean 並沒有辦法直接處理 List<T> 這種東西, 這個 bug 在 2011.10 被提出之後, 2012.11 最後一個 comment 之後就無聲無息了, 目前最新的 GWT 2.5.1 還是有同樣的問題 Zzzz,只能靠 workaround。 後頭會詳述這些事情 [死]。

怎麼用?

好了,終於要進入正題了。 [握拳]

GWT 已經內建 AutoBean,所以不用另外掛 jar 檔, 但是要在 gwt.xml 當中補上:

<inherits name="com.google.web.bindery.autobean.AutoBean"/>

如果你要把下面這個 JSON 字串轉成 Foo 的 instance

{
    "uid":"cde6c847-d072-4d33-82bd-93fa4710dc9b",
    "limit":50,
    "deleted":true,
    "update":1388157386000
}

首先… Foo 得是個 bean 的 interface,定義一堆 getter/setter, 名稱得跟 JSON 裡頭的一致:

interface Foo {
    public void setUid(String uid);
    public void setLimit(int limit);
    public void setDeleted(boolean deleted);
    public void setUpdate(Date update);
    public String getUid();
    public int getLimit();
    public boolean isDeleted();
    public Date getUpdate();
}

轉換的時候需要先建立一個 factory,通常會這樣寫

interface MyFactory extends AutoBeanFactory {
    AutoBean<Foo> foo();
}
MyFactory factory = GWT.create(MyFactory.class);

然後… 終於可以 decode 了:

String foo = "{" +
        "\"uid\":\"cde6c847-d072-4d33-82bd-93fa4710dc9b\"," +
        "\"limit\":50," +
        "\"deleted\":true," +
        "\"update\":1388157380000" +
    "}";
Foo instance = AutoBeanCodex.decode(factory, Foo.class, foo).as();

encode 的話就是:

String fooJson = AutoBeanCodex.encode(
    AutoBeanUtils.getAutoBean(instance)
).getPayload();

如果願意在 MyFactory 裡頭加一個 method:

interface MyFactory extends AutoBeanFactory {
    AutoBean<Foo> foo();
    //下面這個是新增的
    AutoBean<Foo> genFooBean(Foo foo);
}

那不用 AutoBeanUtils.getAutoBean() 而是

String fooJson = AutoBeanCodex.encode(
    factory.genFooBean(instance)
).getPayload();

有沒有比較快樂就見仁見智,不過後面會需要用到後面這招, 或著這麼說比較實在: 「請忘記 AutoBeanUtils 吧」。

注意事項

如果到這邊你還能忍受 AutoBean, 那先講幾個我已經炸到,但可以理解的哏, 主要是跟 GSON 的差異。

  1. Gson.toJson() 遇到 false / null 值不會省略該 field, 但是 AutoBean 會。 也就是說,如果 foo.setDeleted(false);, 那麼 AutoBeanCodex.encode() 出來的字串不會看到 deleted

    當然,這其實不妨礙正常運作。 GSON 的作法可能有些人還會覺得怪?

  2. 日期(java.util.Date)的處理。 Gson.toJson() 會用 Date.toString() 作值(反之亦然), 但是 AutoBean 則是用 Date.getTime()(反之亦然)。 只能說還是統統用 long 表示日期就算了 (然後在 JSON 當中最好還把這數字當成字串, 免得像 32bit 的 PHP 還給你耍花招 [怨念ing])。 至於 Joda 要解決的議題… 遇到再說 XD

有遇到會再補上來 Orz

List 的炸點

如果你永遠不會 decode / encode 一個 array 或是 List, 那恭喜你,除了寫法稍稍扭曲一點之外, AutoBean 是可以接受、也算好用的(應該啦…)。

實際上… 別鬧了,怎麼可能不處理 List

於是 AutoBean 就成了茶几──上頭擺滿了悲劇。

decode

GSON 吐出來的東西來看,一個 List<Foo> 的 instance 會長這樣 (喔對,我把 update 的型態改成 long 了 XD):

[
    {"uid":"cde6c847-d072-4d33-82bd-93fa4710dc9b",
     "limit":50,"deleted":false,"update":"1388157380000"},
    {"uid":"a391dedf-1f81-4380-a712-59eac4d9aea3",
     "limit":50,"deleted":false,"update":"1388157380000"}
]

想依樣畫葫蘆比照辦理時… 等等,AutoBeanCodex.decode() 的第二個參數要給什麼? 然後於是有人弄出了這個 workaround

首先,要建一個 interface 來代表 List<Foo> 這玩意:

interface FooList {
    public void setList(List<Foo> list);
    public List<Foo> getList();
}

factory 的 interface 則是:

interface MyFactory extends AutoBeanFactory {
    //下面這個暫時用不到
    AutoBean<Foo> genFooBean(Foo foo);

    AutoBean<FooList> fooList();
}

最後,要對拿到的 JSON 字串動手腳,變成這樣:

    FooList fooList = AutoBeanCodex.decode(
        factory, FooList.class, "{\"list\":" + foo + "}"
    ).as();
    List<Foo> instance = fooList.getList();

簡單地說,就是 Java 的部份你要讓他有個 class 為依歸, 但是光這樣還不夠,因為 AutoBean 不知道要從何處理起, 所以 JSON 的部份你也要偽造一下……

等等,還沒完,好戲壓箱底、好酒沈甕底, encode 的部份那才叫經典。

encode

要把一個 List<Foo> 轉成 JSON,這到底是有多難? 不難,如果把剛剛 AutoBeanCode.decode() 出來的 fooList 再次轉成 JSON, 那麼只要 factory 加上

interface MyFactory extends AutoBeanFactory {
    //下面這個暫時用不到
    AutoBean<Foo> genFooBean(Foo foo);

    AutoBean<FooList> fooList();
    //下面這個是新增的
    AutoBean<FooList> genFooListBean(FooList instance);
}

立馬就轉,沒有問題!(也完全沒意義 ==”)

AutoBeanCodex.encode(
    factory.genFooListBean(fooList)
).getPayload();

如果是把既有的 List<Foo> instance 轉換, 依照上面的邏輯,得先實做那個毫無意義的 FooList

FooList fooList = new FooList() {
    List<Foo> list = new ArrayList<Foo>();

    @Override
    public void setList(List<Foo> list) {
        this.list = list;
    }

    @Override
    public List<Foo> getList() {
        return list;
    }
};

//FooImpl 就容許我跳過,反正就是 implements Foo 的東東
fooList.getList().add(new FooImpl());

AutoBeanCodex.encode(
    factory.genFooListBean(fooList)
).getPayload();

執行上面這段程式碼,你就會發現 AutoBeanCodex.encode() 快樂的炸了 NPE,而且完全搞不懂發生了什麼事情。

這是個已知的 bug(Issue 6904), 雖然不知道會不會有人去解…… Orz。 而世界還真的是很廣大,有人也找出了 workaround, 解法就是你不能直接丟 FooImpl 的 instance, 得用 MyFactory.genFooBean() 產生出 AutoBean<Foo>, 再藉由它(as())取得 Foo 的 instance 才可以…… 寫的我自己都亂了,看 code 比較實在:

interface MyFactory extends AutoBeanFactory {
    AutoBean<Foo> genFooBean(Foo foo);
    AutoBean<FooList> fooList();
    AutoBean<FooList> genFooListBean(FooList instance);
}

fooList.getList().add(
    //原本是 new FooImpl()
    factory.genFooBean(new FooImpl()).as()
);

AutoBeanCodex.encode(
    factory.genFooListBean(fooList)
).getPayload();

套最近流行的句型: 「如果這不叫脫褲子放屁,我還真他媽的不知道什麼才叫脫褲子放屁」

喔對,無論 genFooBean() 還是 genFooListBean() 都不能用官方文件用的 AutoBeanUtils.getAutoBean()。 如果拿他替換 genFooBean(),一樣噴 NPE; 如果拿它替話 genFooListBean(),不會噴 NPE, 而是轉換出來的 JSON 字串會是 null。

WTF

結尾 murmur

GSONAutoBean 相比是很有趣的。 一個是完美到不需要瞭解內部到底發生什麼事情, 一個則是太糟糕了,所以根本不想瞭解。

短時間之內,我可能還是不會放棄 AutoBean, 除非 GWT 2.6 就遺棄 AutoBean, 或是找到更好的 tool(而不是 GWT-Jackson 那種 style)。 都花了這麼多時間了,就看看能被炸到走到什麼程度。

寫到後來,都不知道到底是在介紹推廣還是在吐槽。 只能說,嗯… 我對 GWT 真的很有愛…… [遠目]

2013年12月15日 星期日

2013 年 GWT 調查報告中文摘要

原始報告請到 http://vaadin.com/gwt/report-2013 下載(要輸入 EMail 帳號)。 除了序言之外,其餘都只翻譯數據跟(個人認定的)重點,非逐句翻譯。


序言

在 2012 年第一次作 GWT 狀況調查 時,收到的回覆數量淹沒了我們。 我們把調查報告取名為《The Furture of GWT》(譯名:GWT 調查報告), 因為我們想知道「GWT 衰老中」這個謠言到底有多少可信度。 在超過 1300 個回應裡,我們、以及全世界都看到了 GWT 活蹦亂跳的樣子。 許多大型企業用 GWT 來建構大型的 application, 對 GWT 投注的資源也在增加。

在《GWT 調查報告 2013 版》中, 我們想要知道 GWT 未來該如何發展?(第六節) 開發團隊在用什麼 Java 跟 GWT 的版本?以瞭解 哪些地方需要支援?(第四節) 你們需要哪些 extension、在找哪些 framework?(第五節) 為了取得背景以及人口統計學的資料, 我們也問了關於你的團隊(第一節)、 你在寫的 application(第二節)、 以及你如何開發 application(第三節)。 有了這些資訊整合成這份報告, 我們覺得這是最綜合性的調查,可以引領 GWT 很長一段路。

在 2013 年,有 12 月的研討會(gwtcreate.com), 有令人興奮的 GWT 3.0 的計畫、 還有許多大公司投入 GWt 作為他們未來的技術; 我們知道 GWT 跨出了不只一大步。 2013 年 GWT 委員會也決定了未來的 release 步調: 每年一個大改版以及一個小改版。

這份調查是由 Vaadin、 Ray Cromwell(Google 代表、現任委員會主席)、 Daniel Kurka(mGWT 與 GWT-phonegap 的 Google 開發者)、 Artur Signell(Vaadin 代表)、 Bhaskar Janakiraman(Google)、 Colin Alworth(Sencha)、 Christian Goudreau(Arcbees)、 Konstantin Solomatov(Jetbrains)、 Mike Brock(RedHat)、 Stephen Haberman(Bizo)、 Joonas Lehtinen(Vaadin)以及 Thomas Broyer。 你可以在這份報告中看到他們的意見與反應。

這份調查報告在 GWT.create 2013 發表, 友超過 600 個熱心的 GWT 開發者出席這個年度最大的 GWT 活動。 我們同樣感謝超過 1400 位受訪者所花的時間與坦白, 期待看到你們的意見!

1. 關於開發團隊

團隊大小

GWT 開發團隊的平均人數是 12.5 人,而中位數則是 8 人。這跟去年的調查相同。 我們也問了過去 12 個月團隊大小的變化。

  • 47%:一樣
  • 41%:擴編
  • 10%:縮編
  • 3%:我只是小員工,不確定…

(兩張團隊大小的統計分佈圖)

團員組成

以八人團隊為例,任務分配為:

  • 2.7 人:後端開發者
  • 2.0 人:前端開發者
  • 1.3 人:測試人員
  • 0.9 人:PM(project / product)
  • 0.6 人:設計師
  • 0.6 人:死星上的人?

地理分佈

  • 58%:歐洲(上升 8%)
  • 25%:北美洲
  • 8%:亞洲(譯註:可惡,我應該去參加調查的 [毆飛])
  • 4%:南美洲
  • 2%:澳洲
  • 2%:非洲

2. GWT 用在哪裡

畫面數量

  • 48%:超過 20
  • 22%:11~20
  • 21%:5~10
  • 10%:1~4

20 是一個魔術數字。調查大型 app 的畫面數量應該很有趣。

Christian:少於 5~7 個畫面的 app 實在太小了,為什麼會用 GWT 呢? 這是個有趣的問題。

compile 時間

  • 1~3 分鐘:27%(去年 31%)
  • 3~10 分鐘:46%(去年 48%)
  • 10~30 分鐘:20%
  • 超過半小時:7%

對照去年的數據,可以代表 project 的規模越來越大。

Ray 與 Bhaskar:要改善 compile 時間有點難、需要一點時間來克服。 不過我們已經開始嘗試 incremental / modular compilation system。

寫給誰用

  • 35%:內部使用
  • 17%:公開且免費
  • 43%:公開但收費
  • 5%:其他

用來寫什麼

  • 46%:商業內部 application
  • 33%:商業外部 application
  • 13%:content-rich 網站
  • 2%:Portlet
  • 1%:遊戲
  • 5%:其他

Ray:看到有人用 GWT 寫遊戲實在很棒。 GWT 可以讓你在 Web 與 Android 之間共用程式碼, 藉由 PlayN 這種 library 也可以拓展到 iOS 上。

browser 支援度

(註:下列數據分別為 希望 2013 年有支援 / 希望 2014 年有支援

  • IE 6 跟 7:14% / 9%
  • IE 8:54% / 44%
  • IE 9:79% / 66%
  • IE 10:80% / 76%
  • IE 11:NaN / 70%
  • Safari:18% / 62%
  • Chrome:94% / 93%
  • Firefox:94% / 92%
  • iOS:NaN / 49%
  • Android:NaN / 50%

Joonas:IE6/7 終於快死光了,所以 GWT 3.0 可以不用鳥它們了。 不過看起來 2014 年還是要很痛苦地去支援 IE8/9, 因為需求量看起來還是蠻大的。

Ray:對於改進 GWT 核心而言,IE6/7/8 是可悲的毒藥, 有很多 API 沒辦法增強就是因為老一輩的 browser 沒辦法處理。 網頁正轉型為 mobile 跟 HTML5, 要整合過時的 browser 會讓這個轉型過程變得很困難。

Thomas:看到 IE6/7/8 加起來還不少,讓我很沮喪。

裝置支援度

(註:下列數據分別為 2012 年 / 2013 年

  • Desktop:98% / 99%
  • Tablet:36% / 45%
  • Phone:26% / 30%
  • 其他:2% / 2%

3. 如何用 GWT 建置 App?

怎麼作 UI?

  • 48%:用 Java 刻
  • 46%:UiBinder
  • 6%:GWT designer

Christian:我不懂為什麼這年頭還會有人想要 Java 刻 UI? 每個語言都有對應的 UI 描述方式。 ActionScript 有 MXML、 JavaScript 有 HTML、 .NET 有 XAML。

Daniel:看到這麼多人用 Java 刻 UI? 明明 UiBinder 就是比較好的方式。我們需要大力推廣 UiBinder。

Mobile App 開發環境

  • 27%:mGWT
  • 16%:gwt-phonegap
  • 16%:JS library
  • 15%:PhoneGap
  • 10%:Vaadin Touchkit
  • 17%:其他

後端通訊方式

  • 53%:GWT-RPC
  • 11%:Request builder
  • 7%:Request factory:
  • 7%:自製
  • 6%:Vaadin
  • 4%:RESTY
  • 3%:gwt-dispatch
  • 3%:gwt-platform

Ray:GWT-RPC 在小型 app、快速開發方面實在超有力的, 但是在 compile 時間以及 interoperability 上會花很多時間。 JAXRS 是 GWT 3.0 考慮的方法之一,可能會提供更好的中間層。

Daniel:在決定 GWT 3.0 以後的 RPC 系統時, 我們應該要記得「很多人喜歡 GWT RPC 的簡單好用」這件事。

IDE

  • 76%:Eclispe
  • 18%:IDEA:
  • 5%:Netbeans
  • 1%:其他

Christian:我個人最愛 IDEA 的 GWT plugin,是目前最成熟的。

Ray:我是 IDEA 的愛好者,它把寫 JSNI/JavaScript 程式碼這檔事做的很好。

如何作測試?

  • 49%:手動
  • 18%:Selenium
  • 13%:JUnit(GWTTest)
  • 13%:沒有測試 UI 這種事啦…

DevMode 還是 Super DevMode?

  • 74%:DevMode
  • 14%:兩個都用
  • 5%:Super DevMode
  • 5%:孟獲孟獲孟獲…

Thomas:看到 Super DevMode 成長真好。 未來要讓所有 browser 支援 DevMode 不太可能。 Super DevMode 的比例成長應該有部份會歸應於 mobile 平台。

Joonas:我們真的應該讓 Super DevMode 變成預設值。 用它來執行程式會比較接近實際的運算效能。 我覺得 DevMode 的優勢在於設定比較方便。

4. 用什麼版本?

GWT 版本

  • 79%:2.5
  • 11%:2.4
  • 3%:2.3
  • 3%:trunk 版
  • 2%:孟獲孟獲孟獲…
  • 1%:2.1
  • 1%:2.0

Thomas:看到有 3% 的人用 thunk 版真的超驚訝的。 我們計畫推出 nightly build,明年這個比率可能會增加。

JDK 版本

  • 37%:Java 6
  • 56%:Java 7
  • 8%:Java 8

Ray:我可以發布一個解法,只讓 client 的碼用 Java8。 但是 Java6 無所不在,希望多一點人可以願意去試試看。 Java8 的 lambdas 超好的, 讓 async callback 還有 collection 操作變得很愉快。

Joonas:Vaadin 7 已經支援 Java 8 了, 我希望我們可以在 GWT 3 當中用到 Lambdas 的優點。

會在 client 端的程式碼用 Java 8 的功能嗎?

  • 52%:不會讓 project 設定變複雜的前提下才會考慮
  • 27%:希望 client 跟 server 端用不同版本的 Java
  • 12%:沒興趣
  • 6%:其他
  • 2%:孟獲孟獲孟獲

Daniel:80% 的人會想要在 GWT 中用 Java8,這相當好。 我們必須找出一個好方法讓 server side 必須用 Java6 的人也能運作。

5. 外掛、framework…

GWT 用起來的產能如何?

  • 不想再用:2%
  • 不是很好:7%
  • 中規中矩:32%
  • 相當優秀:44%
  • 無可匹敵:15%

下一個 project 會用 GWT 嗎?

  • 84%:會
  • 16%:不會

不用 GWT 會用什麼 framework?

  • 5%:Play
  • 5%:JSF
  • 5%:Errai
  • 6%:SmartGWT
  • 8%:Dart
  • 11%:SpringMVC
  • 12%:Vaadin
  • 14%:JavaScript
  • 14%:Angular

Ray:GWT 3.0 會有一個新的 JS interop system, 銜接向 Polymar 或是 Angular 就不用寫 JSNI 的程式碼而變得更容易。 如此一來,想要用什麼 framework 就去用吧。

用哪方面的外掛?

回收的問卷中有 87% 的人使用了外掛,其中:

  • 23%:UI 功能(例如 GWT-dnd、GWT-fx)
  • 20%:application 架構(例如 GWT-presenter)
  • 15%:資料存取(例如 GWT-rpc、GWT-dispatch)
  • 12%:server 存取(例如 Google API、GWT-bootstrap)
  • 9%:application 功能(例如 GWT-platform)
  • 4%:data handling(例如 gilead)
  • 5%:其他
  • 13%:沒有用外掛

外掛去哪找?

  • 55%:就 google 嘛
  • 19%:自幹一個
  • 13%:GitHub
  • 11%:gwtproject.org
  • 2%:其他

你會整合既有 JS 到 project 中嗎?

  • 62%:會
  • 32%:不會
  • 3%:其他
  • 2%:孟獲孟獲孟獲…

6. GWT 的未來

GWT 的 bug

  • 73%:從來沒有回報過 bug
  • 13%:有
  • 14%:有,但是沒有修正

Thomas:GWT 真的夠好嗎?我不確定…… 到底是大家沒有被 bug 炸到,還是他們懶得回報 bug?

GWT 的光明面

每人兩票:

  • 60%:產能(cross-browser、不用弄一堆 JS)
  • 32%:執行速度(不是 compile 速度)
  • 24%:可模組化(可以團隊方式寫 GWT,不會互相影響) (掌控大型、很多功能的程式)
  • 19%:發現 bug 並解決的速度很快
  • 18%:ecosystem 中的開發工具
  • 17%:可靠度
  • 13%:可用度
  • 13%:產能(開始用 GWT 很容易)
  • 9%:DevMode 的重新整理速度
  • 7%:程式碼大小
  • 6%:有很多 widget 可以用
  • 5%:widget 品質很好
  • 5%:設計 application 風格 / application 外觀
  • 3%:有很多外掛
  • 2%:外掛品質很好
  • 2%:compile 時間
  • 3%:其他

GWT 的黑暗面

每人兩票:

  • 55%:compile 時間
  • 29%:DevMode 的重新整理速度
  • 19%:設計 application 風格 / application 外觀
  • 13%:有很多 widget 可以用
  • 13%:程式碼大小 (譯註:圖表的文字是 3%,但是長度是 13% 的大小。用後者。)
  • 11%:發現 bug 並解決的速度很快
  • 10%:產能(開始用 GWT 很容易)
  • 9%:widget 品質很好
  • 7%:ecosystem 中的開發工具
  • 6%:執行速度(不是 compile 速度)
  • 5%:可模組化(可以團隊方式寫 GWT,不會互相影響) (掌控大型、很多功能的程式)
  • 3%:可用度
  • 3%:有很多外掛
  • 2%:外掛品質很好
  • 2%:產能(cross-browser、不用弄一堆 JS)
  • 1%:可靠度
  • 6%:其他

Daniel:GWT 2.6 就已經有一些關於 compile 時間的改善。

Thomas:GWT 3.0 的重點會在改善 compile 時間以及 DevMode 的 refresh 時間上。

對委員會有什麼感覺?

  • 17%:GWT 感覺比以前有活力
  • 22%:對 GWT 的未來比以前有信心
  • 17%:roadmap 更清楚了
  • 28%:沒啥變化耶其實
  • 10%:現在對 GWT 比較沒信心
  • 6%:其他