6

考虑一个不可变类 Foo(一个由 ID 和名称组成的 POJO),需要对其进行序列化,以便将数据从服务器发送到客户端。

public final class Foo
{
    private final int m_id;
    private final String m_displayName;

    private Foo(final int id, final String displayName)
    {
        m_id = id;
        m_displayName = displayName;
    }

    public static Foo create(final int id, final String displayName)
    {
         // Some error checking occurs here.
         . . . 

         m_id = id;
         m_displayName = displayName;
    }

    // Getters etc.
    . . .
}

Foo 对象的实例化通过静态工厂函数发生,并且由于对象是不可变的,因此没有零参数构造函数。

考虑一个不可变的类 Bar ,它包含一个数据成员 Foo 并为其实例化实现了 Builder 模式(从代码片段中省略,因为它与问题无关)。

public final class Bar
{
    private final Foo m_foo;
    . . .

    private Bar(final Builder builder)
    {
        . . .
    }

    public static Builder createBuilder()
    {
        return new Builder();
    }
}

在评估了我应该如何序列化这个对象而不删除它的不变性或添加任何零参数构造函数以进行序列化的选择之后,我得出结论,我需要实现一个 CustomFieldSerializer(对于客户端和服务器)。

我遵循 GWT 的服务器通信文章中编写的指南,并实现了我自己的 CustomFieldSerializer,如下所示。

// Contains the serialization logic of the class Bar.
public final class Bar_CustomFieldSerializerBase
{
    public static Bar instantiate(final SerializationStreamReader streamReader) throws SerializationException
    {
        return Bar.createBuilder().forFoo((Foo) streamReader.readObject()).build();
    }

    public static void serialize(final SerializationStreamWriter streamWriter, final Bar instance)
        throws SerializationException
    {
        // . . .
        streamWriter.writeObject(instance.getFoo());
    }

    public static void deserialize(final SerializationStreamReader streamReader, final Bar instance)
    {
        /*
         * Empty as everything is handled on instantiateInstance().
         */
    }
}

// The CustomFieldSerializer for class Bar. 
public class Bar_CustomFieldSerializer extends CustomFieldSerializer<Bar>
{
    public static void deserialize(final SerializationStreamReader streamReader, final Bar instance) throws SerializationException
    {
    Bar_CustomFieldSerializerBase.deserialize(streamReader, instance);
    }

    public static void serialize(final SerializationStreamWriter streamWriter, final Bar instance) throws SerializationException
    {
        Bar_CustomFieldSerializerBase.serialize(streamWriter, instance);
    }

    public static Bar instantiate(final SerializationStreamReader streamReader) throws SerializationException
    {
        return Bar_CustomFieldSerializerBase.instantiate(streamReader);
    }

    @Override
    public boolean hasCustomInstantiateInstance()
    {
        return true;
    }

    @Override
    public Bar instantiateInstance(final SerializationStreamReader streamReader) throws SerializationException
    {
        return instantiate(streamReader);
    }

    @Override
    public void deserializeInstance(final SerializationStreamReader streamReader, final Bar instance) throws SerializationException
    {
        deserialize(streamReader, instance);
    }

    @Override
    public void serializeInstance(final SerializationStreamWriter streamWriter, final Bar instance) throws SerializationException
    {
        serialize(streamWriter, instance);
    }

// Server side CustomFieldSerializer for class Bar.
public class Bar_ServerCustomFieldSerializer extends ServerCustomFieldSerializer<Bar>
{
    public static void deserialize(ServerSerializationStreamReader streamReader, Bar instance,
        Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException
    {
    /*
     * Empty as everything is handled on instantiateInstance().
     */
    }

    @Override
    public Bar instantiateInstance(ServerSerializationStreamReader streamReader) throws SerializationException
    {
        return Bar_CustomFieldSerializerBase.instantiate(streamReader);
    }

    @Override
    public void deserializeInstance(ServerSerializationStreamReader streamReader, Bar instance,
        Type[] expectedParameterTypes, DequeMap<TypeVariable<?>, Type> resolvedTypes) throws SerializationException
    {
        deserialize(streamReader, instance, expectedParameterTypes, resolvedTypes);
    }

    @Override
    public void deserializeInstance(SerializationStreamReader streamReader, Bar instance) throws SerializationException
    {
        Bar_CustomFieldSerializerBase.deserialize(streamReader, instance);
    }

    @Override
    public void serializeInstance(SerializationStreamWriter streamWriter, Bar instance) throws SerializationException
    {
        Bar_CustomFieldSerializerBase.serialize(streamWriter, instance);
    }
}

由于类 bar 包含一个需要序列化的 Foo 对象,我继续并实现了另一组 CustomFieldSerializers,这一次为类 Foo 遵循相同的模式,用于客户端和服务器。

当 Bar 类发生序列化时会出现问题,特别是在这一点上:

public static void serialize(final SerializationStreamWriter streamWriter, final Bar instance)
        throws SerializationException
{
   // . . .
   streamWriter.writeObject(instance.getFoo());
}

我收到的异常消息如下:

[WARN] Exception while dispatching incoming RPC call com.google.gwt.user.client.rpc.SerializationException: Type 'ui.shared.models.fooItems.Foo' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized.

似乎writeObject()无法序列化 Foo 类型的对象,因为 Foo 类不属于白名单项,即使客户端和服务器都提供了自定义序列化程序。

我总是可以跳过writeObject()调用并为每个 Foo 的数据成员调用writeInt()writeString()(运行良好),但我更愿意让writeObject()工作。我提出的解决方案容易出现维护错误,因为将来 Foo 类的任何更改都必须反映在 Foo 的序列化程序(显而易见)和 Bar 的序列化程序(不太明显)上。

我已经尝试了几乎可以在网上找到的任何东西,从在 Foo 和 Bar 上实现isSerializable接口(没有任何区别,也不应该有任何区别,因为提供自己的自定义序列化程序的 AFAIK 类不需要遵守这条规则),甚至提供一个私有的零参数构造函数(这也不应该有任何区别,因为自定义字段序列化程序的实例化函数应该通过静态工厂处理这个问题)。

为什么 Foo 类没有列入白名单?我是否遗漏了一些明显的东西或误解了一些东西?

提前感谢您的时间。

4

1 回答 1

6

那么这里的问题是,很可能您从未在代码中的任何地方明确提及,Foo该类曾经发送到服务器。例如,您只有这样的服务方法:

interface MyService {

   Foo getFoo(Bar bar); 

}

要使用writeObject,您需要明确提示 GWT,将Foo类包含到反序列化列表中,方法是添加一个以Foo类为参数的新服务方法:

interface MyService {

   Foo getFoo(Bar bar); 

   void setFoo(Foo foo);// letting GWT know that we might send Foo object over the wire, you don't have to ever call this method in your app, or implement it in some meaningful way, just let it be there

}

您不必在您的应用程序中调用此方法,或为其提供任何实现。但否则它将无法正常工作。几乎没有其他方法可以为 GWT 创建此提示,但想法是相同的,您将为 GWT-RPC 显式公开 Foo 类。另请记住,如果您Bar在多个服务中使用类,则必须将此类方法添加到使用Bar类的每个服务中

现在,详细说明为什么会发生这种情况。在服务器端,GWT-RPC 跟踪两个列表:可以序列化的对象和可以反序列化的对象。此信息取自 RPC 策略清单。在我的第一个示例中,我只提到Bar了对象作为可以发送到服务器的东西。但是由于您已经在类上定义了一个自定义字段序列化Bar器,GWT 不会对 执行任何分析Bar,因此它不知道实例Foo可能会发送到服务器,因此它决定服务器端不需要反序列化器Foo。因此,当您尝试通过网络发送实例时Bar,服务器会尝试反序列化它,但是由于readObject在自定义序列化程序中使用,它也尝试为 Foo 查找反序列化程序,但不允许这样做,因此整个反序列化过程失败。如果您添加额外的服务方法,它只Foo通过线路发送对象,GWT 会意识到这样的对象也可以发送到服务器,因此它也标记Foo为可反序列化。

这非常令人困惑,我个人认为应该将具有自定义序列化的类自动添加到所有白名单中,但事实就是如此。

编辑

另一个(没有愚蠢的空方法的非 hacky 解决方案)将使用专门的 DTO 层进行通信(例如,仅具有默认公共构造函数的 POJO),但在您需要发送具有大量交叉的复杂对象图的情况下可能会很困难参考。

于 2013-02-27T22:42:27.303 回答