ประเภท AutoBean แบบกำหนดพารามิเตอร์ที่มีสมาชิกที่พิมพ์

คำถาม

มีวิธีใดบ้างในการดีซีเรียลไลซ์ JSON โดยใช้เฟรมเวิร์ก AutoBean เพื่อให้ bean ผลลัพธ์มีพารามิเตอร์ประเภทที่ส่งผลต่อประเภทของสมาชิกตั้งแต่หนึ่งตัวขึ้นไป

พื้นหลัง

RPC พร้อมผลลัพธ์ JSON

ฉันใช้ GWT (RequestBuilder) เพื่อดำเนินการคำขอ RPC เพย์โหลด JSON ที่ส่งคืนมีรูปแบบต่อไปนี้:

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

วัตถุใน resultSet มีประเภทแตกต่างกันไปขึ้นอยู่กับ RPC เฉพาะที่ฉันกำลังเรียก

อินเตอร์เฟซออโต้บีน

ฉันต้องการดีซีเรียลไลซ์ JSON นี้โดยใช้ AutoBean ฉันพยายามเป็นตัวแทนของวัตถุนี้ดังนี้:

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() กำลังส่งกลับค่า raw List
  2. โปรแกรมดีซีเรียลไลเซอร์ AutoBean พยายามสร้างอินสแตนซ์ Object สำหรับแต่ละรายการใน resultSet
  3. ความล้มเหลว

คำถามอีกครั้ง

ฉันขาดอะไรบางอย่างใน AutoBean API ที่จะทำให้ฉันทำสิ่งนี้ได้อย่างง่ายดายหรือไม่ ถ้าไม่ มีจุดโจมตีที่ชัดเจนที่ฉันควรพิจารณาหรือไม่ มีทางเลือกอื่นที่สมเหตุสมผลกว่าสำหรับสิ่งที่ฉันทำอยู่ (นอกเหนือจาก JSONParser และ JavaScriptObject ที่ฉันใช้อยู่แล้ว) หรือไม่


person Wesley    schedule 15.03.2011    source แหล่งที่มา


คำตอบ (2)


นี่ไม่ใช่เรื่องง่าย เนื่องจากการลบประเภท 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();
  }
}
person BobV    schedule 15.03.2011
comment
แนวทางนี้สมเหตุสมผลสำหรับฉันจริงๆ อย่างไรก็ตาม แม้ว่าจะใช้ GWT 2.2.0 แต่ดูเหมือนว่าประเภททั่วไปจะไม่ทำงานเป็นพารามิเตอร์สำหรับวิธีการจัดหมวดหมู่ ซึ่งหมายความว่าเป็นไปไม่ได้ที่จะส่งคลาสอ็อบเจ็กต์ไปที่ getResultAs ฉันทำให้ตัวอย่างของคุณง่ายขึ้นเป็น paste.pocoo.org/show/354352 แต่ คอมไพเลอร์ GWT บ่นกับ paste.pocoo.org/show/354358 หากไม่สามารถส่งผ่านออบเจ็กต์ของคลาสได้ โซลูชันที่ค่อนข้างหรูหรานี้ก็ใช้งานไม่ได้ - person Wesley; 16.03.2011
comment
ฉันคิดว่าคุณพิมพ์ผิดในหมวดหมู่ของคุณ คุณได้เพิ่มเมธอด getResultSet() แทนที่จะตั้งชื่อว่า getResultAs() - person BobV; 16.03.2011
comment
คุณถูก. การแก้ไขการพิมพ์ผิดจะแก้ไขตัวพิมพ์เล็กที่ฉันวางไว้ พยายามที่จะเข้าใกล้โค้ดในคำตอบของคุณมากขึ้น ฉันลงเอยด้วย paste.pocoo.org/show/ 354669. อย่างไรก็ตาม สิ่งนี้ทำให้เกิดข้อผิดพลาดในการคอมไพล์ของ paste.pocoo.org/show/354671 (ฉันลองทั้ง AutoBean<ResultContainer<T>> bean และ AutoBean<ResultContainer> bean ในลายเซ็นของ ResultCategory.getResultAs แล้วผลลัพธ์ก็เหมือนกัน) ฉันยังคงทำผิดพลาดที่ชัดเจนที่นี่หรือไม่ - person Wesley; 16.03.2011

ลองแทนที่เมธอด .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 จะทำให้การแทนที่นั้นเสียหาย

person Ivan B    schedule 04.05.2011