包含类型化成员的参数化 AutoBean 类型
Posted
技术标签:
【中文标题】包含类型化成员的参数化 AutoBean 类型【英文标题】:A parameterized AutoBean type containing a typed member 【发布时间】:2011-03-15 03:39:08 【问题描述】:问题
是否有任何方法可以使用 AutoBean 框架反序列化 JSON,以便生成的 bean 具有影响其一个或多个成员的类型的类型参数?
背景
带有 JSON 结果的 RPC
我正在使用 GWT (RequestBuilder
) 来执行 RPC 请求。返回的 JSON 有效负载格式如下:
"resultSet": [..., ..., ...], // items requested; say, items 150-160
"totalCount": 15330 // total matching items in DB
resultSet
中的对象的类型取决于我调用的特定 RPC。
AutoBean 接口
我想使用 AutoBean 反序列化这个 JSON。我试图将这个对象表示如下:
interface RpcResults<T>
List<T> getResultSet();
void setResultSet(List<T> resultSet);
int getTotalCount();
void setTotalCount(int totalCount);
我还创建了适当的接口来表示resultSet
中可能存在的每种对象类型。最后,我设置了对AutoBeanCodex.decode
的适当调用。
运行代码
尝试在开发模式下运行此代码会导致控制台中出现以下堆栈跟踪:
19:44:23.791 [ERROR] [xcbackend] Uncaught exception escaped
java.lang.IllegalArgumentException: The AutoBeanFactory cannot create a java.lang.Object
at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.push(AutoBeanCodex.java:240)
at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.decode(AutoBeanCodex.java:50)
at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.visitCollectionProperty(AutoBeanCodex.java:83)
at com.citrix.xenclient.backend.client.json.RpcResultsAutoBean.traverseProperties(RpcResultsAutoBean.java:100)
at com.google.gwt.autobean.shared.impl.AbstractAutoBean.traverse(AbstractAutoBean.java:153)
at com.google.gwt.autobean.shared.impl.AbstractAutoBean.accept(AbstractAutoBean.java:112)
at com.google.gwt.autobean.shared.AutoBeanCodex$Decoder.decode(AutoBeanCodex.java:51)
at com.google.gwt.autobean.shared.AutoBeanCodex.decode(AutoBeanCodex.java:505)
at com.google.gwt.autobean.shared.AutoBeanCodex.decode(AutoBeanCodex.java:521)
at com.citrix.xenclient.backend.client.services.JSONResponseResultSetHandler.onResponseReceived(JSONResponseResultSetHandler.java:51)
at com.google.gwt.http.client.Request.fireOnResponseReceived(Request.java:287)
at com.google.gwt.http.client.RequestBuilder$1.onReadyStateChange(RequestBuilder.java:395)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:616)
at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:157)
at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessagesWhileWaitingForReturn(BrowserChannelServer.java:326)
at com.google.gwt.dev.shell.BrowserChannelServer.invokejavascript(BrowserChannelServer.java:207)
at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:126)
at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:561)
at com.google.gwt.dev.shell.ModuleSpace.invokeNativeObject(ModuleSpace.java:269)
at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeObject(JavaScriptHost.java:91)
at com.google.gwt.core.client.impl.Impl.apply(Impl.java)
at com.google.gwt.core.client.impl.Impl.entry0(Impl.java:214)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:616)
at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:157)
at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessages(BrowserChannelServer.java:281)
at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:531)
at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:352)
at java.lang.Thread.run(Thread.java:636)
基于此堆栈跟踪,我的预感如下:
-
类型擦除使
RpcResults.getResultSet()
似乎返回原始List
。
AutoBean 反序列化器尝试为resultSet
中的每个项目创建Object
实例。
失败
再次提问
我是否遗漏了 AutoBean API 中可以让我轻松做到这一点的东西?如果没有,我应该研究一个明显的攻击点吗?对于我正在做的事情(除了我已经在使用的 JSONParser 和 JavaScriptObject),是否有更明智的选择?
【问题讨论】:
【参考方案1】:这并不简单,因为 Java 类型擦除。 T
类型在运行时不存在,已被擦除为 Object
以代替任何其他下限。 AutoBeanCodex 需要类型信息才能具体化传入 json 有效负载的元素。此类型信息通常由 AutoBean 实现提供,但由于 T
擦除,它只知道它包含 List<Object>
。
如果您可以在运行时提供类文字,则可以将 getter 声明为 Splittable getResultSet()
,并通过调用 AutoBeanCodex.decode(autoBeanFactory, SomeInterfaceType.class, getResultSet().get(index))
来具体化列表的各个元素。通过使用Category
,您可以将<T> T getResultAs(Class<T> clazz, int index)
方法添加到AutoBean 接口。这看起来像:
@Category(MyCategory.class)
interface MyFactory extends AutoBeanFactory
AutoBean<ResultContainer> resultContainer();
interface ResultContainer<T>
Splittable getResultSet();
// It's the class literal that makes it work
T getResultAs(Class<T> clazz, int index);
class MyCategory
public static <T> T getResultAs(Autobean<ResultContainer> bean,
Class<T> clazz, int index)
return AutoBeanCodex.decode(bean.getFactory(), clazz,
bean.as().getResultSet().get(index)).as();
【讨论】:
这种方法对我来说真的很有意义。然而,即使在 GWT 2.2.0 中,泛型类型似乎也不能作为类别方法的参数。这意味着不可能将类对象传递给getResultAs
。我已将您的示例简化为 paste.pocoo.org/show/354352,但 GWT 编译器抱怨 paste.pocoo.org/show/354358。如果不能传入类对象,这个相当优雅的解决方案就行不通。
我认为您的类别中有错字。您添加了一个方法 getResultSet() 而不是将其命名为 getResultAs()。
你是对的。修复该错字修复了我粘贴的简化案例。试图更接近您答案中的代码,我最终得到paste.pocoo.org/show/354669。但是,这会导致paste.pocoo.org/show/354671 的编译错误。 (我在ResultCategory.getResultAs
的签名中尝试了AutoBean<ResultContainer<T>> bean
和AutoBean<ResultContainer> bean
,结果相同。)我这里还有什么明显的错误吗?【参考方案2】:
尝试在特定于对象的接口中覆盖 .getResultSet() 和 .setResultSet() 方法:
interface FooRpcResults extends RpcResults<Foo>
@Override
List<Foo> getResultSet();
@Override
void setResultSet(List<Foo> value);
以下测试适用于我(GWT 2.3.0):
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.autobean.vm.AutoBeanFactorySource;
public class AutoBeanTest
public static interface Page<T>
int getDataSize();
List<T> getPage();
int getStartIndex();
void setDataSize(int value);
void setPage(List<T> value);
void setStartIndex(int value);
public static interface Thing
String getName();
void setName(String value);
public static interface ThingFactory extends AutoBeanFactory
AutoBean<Thing> createThing();
AutoBean<ThingPage> createThingPage();
public static interface ThingPage extends Page<Thing>
@Override
List<Thing> getPage();
@Override
void setPage(List<Thing> value);
@Test
public void testAutoBean()
final ThingFactory factory = AutoBeanFactorySource
.create(ThingFactory.class);
final Thing thing1 = factory.createThing().as();
thing1.setName("One");
final Thing thing2 = factory.createThing().as();
thing2.setName("Two");
final List<Thing> things = new ArrayList<Thing>();
things.add(thing1);
things.add(thing2);
final Page<Thing> page = factory.createThingPage().as();
page.setStartIndex(50);
page.setDataSize(1000);
page.setPage(things);
final String json = AutoBeanCodex.encode(
AutoBeanUtils.getAutoBean(page)).getPayload();
final Page<Thing> receivedPage = AutoBeanCodex.decode(factory,
ThingPage.class, json).as();
assertEquals(receivedPage.getStartIndex(), page.getStartIndex());
assertEquals(receivedPage.getDataSize(), page.getDataSize());
assertNotNull(receivedPage.getPage());
assertEquals(receivedPage.getPage().size(), page.getPage().size());
for (int i = 0; i < receivedPage.getPage().size(); i++)
assertNotNull(receivedPage.getPage().get(i));
assertEquals(receivedPage.getPage().get(i).getName(), page
.getPage().get(i).getName());
删除 ThingPage 界面中的覆盖会破坏它。
【讨论】:
以上是关于包含类型化成员的参数化 AutoBean 类型的主要内容,如果未能解决你的问题,请参考以下文章