作者PsMonkey (痞子军团团长)
看板java
标题GWT 的 AutoBean
时间Sun Dec 29 14:56:39 2013
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