GWT自定义字段序列化问题

时间:2013-02-25 15:25:32

标签: java gwt serialization

考虑一个不可变类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对象的实例化是通过静态工厂函数进行的,因为对象是不可变的,所以没有零参数构造函数。

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

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

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

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

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

我遵循了GWT Server Communication文章中的指导原则,并实现了我自己的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);
    }
}

由于类栏包含需要序列化的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()调用并调用 writeInt()&amp; writeString()为每个Foo的数据成员(运行良好)但我宁愿让 writeObject()工作。我提出的解决方案高度容易出现维护错误,因为未来Foo类中的任何更改都必须反映在Foo的序列化器(显而易见的)和Bar的序列化器(不那么明显)上。 / p>

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

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

感谢您提前抽出时间。

1 个答案:

答案 0 :(得分: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查找反序列化器,但它是不允许,因此整个deserealization过程失败。如果添加通过线路仅发送Foo对象的额外服务方法,GWT也会意识到此类对象也可以发送到服务器,因此它也将Foo标记为可反序列化。

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

修改

另一个(非hacky解决方案,没有愚蠢的空方法),将使用专门的DTO层进行通信(例如,只有POJO与默认的公共构造函数),但是当你需要发送复杂的对象图时,可能很难很多交叉引用。