GWT 的 AutoBean
blog 版:http://blog.dontcareabout.us/2013/12/gwt-autobean.html
BBS 版以 markdown 語法撰寫
______________________________________________________________________
[AutoBean] 是目前打算拿來在 GWT 中處理 JSON 的工具。
不過在講正事之前,先扯兩段雜談 [毆飛]
[AutoBean]: https://code.google.com/p/google-web-toolkit/wiki/AutoBean
## 雜談 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 上也可以使用。
[GSON]: https://code.google.com/p/google-gson/
[TypeAdapterFactory 的用法]: #1IVxn9h0 (java)
[bGwtGson]: https://github.com/heroandtn3/bGwtGson
## 雜談 2:謎樣裏技? ##
我搞不太懂 [AutoBean] 在 GWT 當中扮演什麼樣的角色?
彷彿還蠻多人在用的(因為大家都炸同樣的問題... Orz),
但是官方指南似乎沒有這個東西(JavaDoc 當然還是有),
教學文件只有出現在 google code 的 wiki 上,
所以這是裏技嗎?
我比較怕這是即將被拋棄的裏技 Orz
畢竟要在 GWT 裡頭處理 JSON 並不是太麻煩。
官方指南建議的 JSNI / Overlay Types 寫起來堪稱簡單直覺,
尤其是跟 [AutoBean] 相比的話 [死]。
再不然 [GWT-Jackson] 好像也是種選擇?
剛好兩者的風格我都不愛 [淚目]
最大的哏在於 [AutoBean] 並沒有辦法直接處理 `List<T>` 這種東西,
[這個 bug][Issue 6904] 在 2011.10 被提出之後,
2012.11 最後一個 comment 之後就無聲無息了,
目前最新的 GWT 2.5.1 還是有同樣的問題 Zzzz,只能靠 workaround。
後頭會詳述這些事情 [死]。
[GWT-Jackson]: https://github.com/nmorel/gwt-jackson
[Issue 6904]: https://code.google.com/p/google-web-toolkit/issues/
detail?id=6904
## 怎麼用? ##
好了,終於要進入正題了。 [握拳]
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*)
[Joda]: http://www.joda.org/joda-time/
## `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][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 的部份那才叫經典。
[decode workaround]: http://stackoverflow.com/questions/13651068/
gwt-autobean-how-to-handle-lists
### 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][encode 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
[encode workaround]: https://groups.google.com/d/msg/google-web-toolkit/nvIotNHy-Io/GkXz_WQXvR4J
## 結尾 murmur ##
拿 [GSON] 跟 [AutoBean] 相比是很有趣的。
一個是完美到不需要瞭解內部到底發生什麼事情,
一個則是太糟糕了,所以根本不想瞭解。
短時間之內,我可能還是不會放棄 [AutoBean],
除非 GWT 2.6 就遺棄 [AutoBean],
或是找到更好的 tool(而不是 [GWT-Jackson] 那種 style)。
都花了這麼多時間了,就看看能<strike>被炸到</strike>走到什麼程度。
寫到後來,都不知道到底是在介紹推廣還是在吐槽。
只能說,嗯... 我對 GWT 真的很有愛...... [遠目]
--
錢鍾書: 說出來的話
http://www.psmonkey.org
比不上不說出來的話
Java 版 cookcomic 版
只影射著說不出來的話
and more......
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.115.237.204
java 近期熱門文章
PTT數位生活區 即時熱門文章