AutoBean 框架

GWT AutoBean 框架提供自动生成的 bean 类接口实现,以及这些接口的低级序列化机制。AutoBeans 可用于 _客户端和服务器代码_,以提高代码复用。例如,RequestFactory 系统在客户端和服务器代码中都大量使用 AutoBeans。

  • 目标

    • 减少模型丰富应用程序中的样板代码
    • 支持将 AutoBeans 轻松编码为 JSON 结构
    • 为数据模型对象上的常见操作提供支持代码
    • 可在非 GWT(例如服务器)代码中使用
  • 非目标

    • 支持非 DAG 数据结构
    • 支持对象标识语义
    • 如果需要更高阶的模型语义,AutoBeans 的非目标是 RequestFactory 框架的关键功能。

内容

快速入门

在您的 module.xml 文件中,添加

<inherits name="com.google.web.bindery.autobean.AutoBean"/>

然后在您的源代码中

// Declare any bean-like interface with matching getters and setters, no base type is necessary
interface Person {
  Address getAddress();
  String getName();
  void setName(String name);
  void setAddress(Address a);
}

interface Address {
  // Other properties, as above
}

// Declare the factory type
interface MyFactory extends AutoBeanFactory {
  AutoBean<Address> address();
  AutoBean<Person> person();
}

class DoSomething() {
  // Instantiate the factory
  MyFactory factory = GWT.create(MyFactory.class);
  // In non-GWT code, use AutoBeanFactorySource.create(MyFactory.class);

  Person makePerson() {
    // Construct the AutoBean
    AutoBean<Person> person = factory.person();

    // Return the Person interface shim
    return person.as();
  }

  String serializeToJson(Person person) {
    // Retrieve the AutoBean controller
    AutoBean<Person> bean = AutoBeanUtils.getAutoBean(person);

    return AutoBeanCodex.encode(bean).getPayload();
  }

  Person deserializeFromJson(String json) {
    AutoBean<Person> bean = AutoBeanCodex.decode(factory, Person.class, json);
    return bean.as();
  }
}

属性类型

以下类型可用于组合 AutoBean 接口

  • 值类型

    • 原始类型及其包装类型
    • BigIntegerBigDecimal
    • java.util.Date
    • 枚举类型
    • 字符串
  • 引用类型

    • bean 类接口
    • 任何支持的属性类型的列表或集合
    • 任何支持的属性类型的映射

AutoBean

AutoBean 必须使用接口类型参数化(例如 AutoBean<Person>)。此接口类型可以具有任何类型层次结构,并且不需要扩展任何特定类型才能与 AutoBeans 一起使用。区分目标接口是否为“简单”。

简单接口满足以下属性

  • 只有 getter 和 setter 方法
  • 任何非属性方法必须由 类别 实现

可以通过 AutoBeanFactory 构建简单 AutoBean,而无需提供委托实例。

如果从目标接口中的方法返回引用接口,则该实例将自动由 AutoBean 实例包装。可以通过在 AutoBeanFactory 上放置 @NoWrap 注释来禁用此行为。

accept()

AutoBean 控制器提供了一个访问者 API,允许通过对包装的接口没有先验知识的代码来检查 AutoBean 的属性。

as()

AutoBean 充当 shim 对象的控制器,该对象实现了 AutoBean 参数化的接口。例如,为了获取 AutoBean<Person>Person 接口,有必要调用 as() 方法。进行这种间接级别的原因是避免如果 AutoBean 也实现其目标接口,则可能出现的方法签名冲突。

clone()

可以克隆 AutoBean 及其存储其中的属性值。clone() 方法有一个布尔参数,将触发深层或浅层复制。与 AutoBean 关联的任何标签值都不会被克隆。包装委托对象的 AutoBeans 不能被克隆。

getTag() / setTag()

可以通过将 AutoBean 用作类似于 map 的对象,将任意类型的任意元数据与 AutoBean 关联起来。标签值不参与克隆或序列化操作。

isFrozen() / setFrozen()

可以通过调用 setFrozen() 来禁用属性修改。任何尝试在冻结的 AutoBean 上调用 setter 都会导致 IllegalStateException

isWrapper() / unwrap()

如果用于实例化 AutoBean 的工厂方法提供了委托对象,则可以通过调用 unwrap() 对象来分离 AutoBean。isWrapper() 方法将指示

AutoBeanFactory

无需为每个 AutoBean 实例调用 GWT.create(),只需使用 GWT.create()AutoBeanFactorySource.create()(在 2.1.1 和 2.2 中,AutoBeanMagicSource 被命名为 AutoBeanFactoryMagic)构建 AutoBeanFactory 即可。这允许 AutoBeanFactory 通过任何所需的依赖注入模式提供给使用代码。

AutoBeanFactory 接口中的方法必须返回 AutoBean<Foo>,其中 Foo 是与 AutoBeans 兼容的任何接口类型。这些方法可以选择声明类型为 Foo 的单个参数,这允许围绕现有对象构建 AutoBean。

interface MyFactory extends AutoBeanFactory {
  // Factory method for a simple AutoBean
  AutoBean<Person> person();

  // Factory method for a non-simple type or to wrap an existing instance
  AutoBean<Person> person(Person toWrap);
}

create()

create() 方法接受任何从 AutoBeanFactory 接口可达的接口类型的 Class 对象。可选参数允许提供委托对象,该对象将由返回的 AutoBean 包装。

AutoBeanCodex

AutoBeanCodex 提供了将 AutoBeans 编码和解码为 JSON 格式的有效负载的通用用途。

decode()

此方法接受 AutoBeanFactory、表示要返回的顶级 AutoBean 接口类型的 Class 对象以及 JSON 格式的有效负载。提供的 AutoBeanFactory 必须能够为从提供的接口可达的所有接口类型生成 AutoBeans。

encode()

此方法接受 AutoBean 并返回一个 Splittable,该 Splittable 代表一个 JSON 有效负载,其中包含 AutoBean 及其关联的对象图的属性。

Splittable

Splittable 类型是围绕低级线格式和用于操作线格式的库的抽象。例如,在客户端代码中,Splittable 是一个 JavaScriptObject,而在服务器端,它由 org.json 库支持。该接口提供允许查询底层数据模型的方法。

每当 AutoBeanCodex 遇到 Splittable 属性或 Splittable 集合时,Splittable.getPayload() 方法返回的内容将直接注入线格式。

Splittable 类型允许通过不同的 AutoBeanFactory 类型支持的消息对象在一个有效负载中组合,因为 Splittable 必须通过 AutoBeanCodex.decode() 显式解码。

AutoBeanVisitor

AutoBeanVisitor 是一个具体的、无操作的基类型,旨在由希望在 AutoBean 的目标接口上编写类似于反射的代码的开发人员扩展。

visit() / endVisit()

无论 AutoBean 图的引用结构如何,访问者都会精确地访问一次任何给定的 AutoBean。AutoBeanVisitor 的用户无需自己实现循环检测。

从 GWT 2.1.1 开始,Context 接口为空,存在是为了允许未来扩展。

visitReferenceProperty() / visitValueProperty()

AutoBeanVisitor 类型中的属性访问方法将接收一个 PropertyContext 对象,该对象允许修改属性的值,并提供有关该字段的类型信息。在调用 set() 之前调用 canSet() 方法可以促进良好的代码卫生。

visitCollectionProperty() / visitMapProperty()

这些访问方法的行为类似于 visitReferenceProperty(),但是传递到这些方法中的 PropertyContext 是专门的,以提供 CollectionMap 对象的参数化。

AutoBeanUtils

diff()

对两个 AutoBeans 中的属性进行浅层比较,并返回一个映射,其中包含彼此不相等的属性。这些 bean 不需要具有相同的接口类型,这允许一定程度的鸭子类型。

getAllProperties()

创建 AutoBean 中属性的浅层副本。修改返回的映射的结构不会对 AutoBean 的状态有任何影响。映射中的引用值不会被克隆,而是 AutoBean 属性持有的相同实例。

JSON 结构

AutoBean 框架可以用作 JSON 互操作性层,为现有的 JSON api 提供 Java 类型系统包装器,或创建 JSON 有效负载以与远程服务进行交互。这可以通过根据 JSON 架构设计 Java API 来实现。@PropertyName 注释可以应用于 getter 和 setter,其中 Java 命名约定与 JSON 架构不一致。

一般来说,AutoBeanCodex 发出的对象的序列化形式反映了接口声明。例如,本文档快速入门部分中描述的示例 Person 接口可能被序列化为

// Whitespace added for clarity
{ "name" : "John Doe", "address" : { "street" : "1234 Maple St", "city" : "Nowhere" } }`

列表和集合属性被编码为 JSON 列表。例如,List<Person> 将被编码为

[ { "name" : "John Doe" } , { "name" : "Jim Smith" } ]

映射以两种形式序列化,具体取决于键类型是值类型还是引用类型。值映射被编码为典型的 JSON 对象。例如,Map<Integer, Foo> 将被编码为

{ "1" : { "property" : "value"}, "55" : { "property" : "value" } }

使用引用对象作为键的映射将被编码为两个列表的列表。这允许包含具有相同序列化形式的键的对象标识映射被正确地反序列化。例如,一个 Map<Person, Address> 将被编码为

[ 
  [ { "name" : "John Doe" } , { "name" : "Jim Smith" } ],
  [ { "street" : "1234 Maple Ave" }, { "street" : "5678 Fair Oaks Lane" } ]
]

Java 枚举值被写成枚举值的字符串名称。这可以通过在枚举字段声明中应用 PropertyName 注解来覆盖。使用名称而不是序号值将允许有效负载对端点模式偏差具有鲁棒性。

类别

纯 Bean 接口只能在构建有用的系统方面走这么远。例如,RequestFactory 使用的 EntityProxy 类型是一个 AutoBean 接口,除了添加 stableId() 方法之外。如果由类别提供任何非属性接口的实现,AutoBeanFactory 可以生成非包装(也称为“简单”)的非简单接口实例。

interface Person {
  String getName();
  void setName(String name);
  boolean marry(Person spouse);
}

@Category(PersonCategory.class)
interface MyFactory {
  // Would be illegal without a category providing an implementation of marry(AutoBean<Person> person, Person spouse)
  AutoBean<Person> person();
}

class PersonCategory {
  public static boolean marry(AutoBean<Person> instance, Person spouse) {
    return new Marriage(instance.as(), spouse).accepted();
  }
}

对于任何非属性方法,类别必须声明一个公共静态方法,该方法具有一个额外的第 0 个参数,该参数接受支持实例的 AutoBean。RequestFactory 中演示 stableId() 方法实现的另一个示例

class EntityProxyCategory {
  EntityProxyId<?> stableId(AutoBean<EntityProxy> instance) {
    return (EntityProxyId<?>) instance.getTag("stableId");
}

@Category 注解可以指定多个类别类型。将选择第一个类别中名称与类型可分配的非属性方法匹配的第一个方法。在做出此决定时,会检查第 0 个参数 AutoBean 的参数化。

拦截器

类别实现还可以声明一个拦截器方法来检查并可能替换目标接口中所有非 void 方法的返回值

public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
  // Do stuff
  return maybeAlteredReturnValue;
}

RequestFactory 使用它使从可编辑对象返回的 EntityProxy 对象可编辑。