包含类型化成员的参数化 AutoBean 类型

发布于 2024-10-21 20:23:44 字数 4678 浏览 3 评论 0原文

问题

有没有办法使用 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)

根据此堆栈跟踪,我的预感如下:

  1. 类型擦除使得看起来 RpcResults.getResultSet() 返回一个原始的 List
  2. AutoBean 反序列化器尝试为 resultSet 中的每个项目创建 Object 实例。
  3. 再次失败

问题

我是否在 AutoBean API 中缺少一些可以让我轻松完成此操作的东西?如果不是,是否有我应该调查的明显攻击点?对于我正在做的事情,是否有更明智的替代方案(除了我已经在使用的 JSONParser 和 JavaScriptObject 之外)?

Question

Is there any way to deserialize JSON using the AutoBean framework such that the resulting bean has a type parameter that affects the type of one or more of its members?

Background

RPC with JSON results

I'm using GWT (RequestBuilder) to perform RPC requests. The JSON payload returned is of the following form:

{
  "resultSet": [{...}, {...}, ...], // items requested; say, items 150-160
  "totalCount": 15330               // total matching items in DB
}

The objects in resultSet vary in type depending on the specific RPC I'm calling.

AutoBean interface

I'd like to deserialize this JSON using AutoBean. I'm trying to represent this object as follows:

interface RpcResults<T> {

  List<T> getResultSet();
  void setResultSet(List<T> resultSet);

  int getTotalCount();
  void setTotalCount(int totalCount);

}

I've also created appropriate interfaces representing each type of object that could exist within resultSet. Finally, I set up the appropriate call to AutoBeanCodex.decode.

Running the code

Attempting to run this code in development mode causes the following stack trace to appear in the console:

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)

Based on this stack trace, my hunch is the following:

  1. Type erasure makes it seem that RpcResults.getResultSet() is returning a raw List.
  2. The AutoBean deserialiser attempts to create Object instances for each item in resultSet.
  3. Failure

Question again

Am I missing something in the AutoBean API that will allow me to do this easily? If not, is there an obvious point of attack I should look into? Is there a more sensible alternative for what I'm doing (other than JSONParser and JavaScriptObject, which I'm already using)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

哎呦我呸! 2024-10-28 20:23:44

由于 Java 类型擦除,这并不简单。类型 T 在运行时不存在,已被擦除为 Object 以代替任何其他下限。 AutoBeanCodex 需要类型信息才能具体化传入 json 有效负载的元素。此类型信息通常由 AutoBean 实现提供,但由于 T 擦除,它只知道它包含一个 List

如果您可以在运行时提供类文字,则可以将 getter 声明为 Splittable getResultSet() ,并通过调用 AutoBeanCodex.decode(autoBeanFactory, SomeInterfaceType.class, getResultSet().get(index))。通过使用 类别,您可以添加; AutoBean 接口的 T getResultAs(Classclazz, int index) 方法。这看起来像:

@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();
  }
}

This is not simple, due to Java type erasure. The type T does not exist at runtime, having been erased to Object in lieu of any other lower bound. The AutoBeanCodex requires type information in order to reify the elements of the incoming json payload. This type information is usually provided by the AutoBean implementation, but due to the T erasure, all it knows is that it contains a List<Object>.

If you can provide a class literal at runtime, the getter could be declared as Splittable getResultSet() and the individual elements of the list reified by calling AutoBeanCodex.decode(autoBeanFactory, SomeInterfaceType.class, getResultSet().get(index)). By using a Category, you could add a <T> T getResultAs(Class<T> clazz, int index) method to the AutoBean interface. This would look something like:

@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();
  }
}
溺ぐ爱和你が 2024-10-28 20:23:44

尝试重写特定于对象的接口中的 .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 接口中的重写将会破坏它。

Try overriding the .getResultSet() and .setResultSet() methods in your object-specific interfaces:

interface FooRpcResults extends RpcResults<Foo> {
    @Override
    List<Foo> getResultSet();
    @Override
    void setResultSet(List<Foo> value);
}

The following test works for me (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());
  }
 }
}

Removing the overrides in the ThingPage interface will break it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文