Java GWT自定义字段序列化问题

Java GWT自定义字段序列化问题,java,gwt,serialization,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_displayNa

考虑一个不可变的类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,并为其实例化实现构建器模式(从代码段中省略,因为它与问题无关)

在评估了我对序列化此对象的方式的选择后,我得出结论:我需要实现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);
    }
}
我得到的异常消息如下:

[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类没有被列入白名单?我是否错过了一些明显的东西或者误解了一些东西


提前感谢您的时间。

这里的问题是,您很可能从未明确提到代码中的任何地方,
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
对象作为可以发送到服务器的对象。但是,由于由于GWT在
Bar
类上安装了自定义字段序列化程序,所以GWT不会对
Bar
执行任何分析,因此它不知道
Foo
的实例可能会被发送到服务器,因此它决定服务器端的
Foo
不需要反序列化程序ire,服务器尝试对其进行反序列化,但由于自定义序列化程序中使用了
readObject
,因此它也尝试查找Foo的反序列化程序,但这是不允许的,因此整个反序列化过程失败。如果您添加额外的服务方法,该方法仅通过线路发送
Foo
对象,GWT会意识到这样的obobject也可以发送到服务器,因此它也将
Foo
标记为可反序列化

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

编辑


另一种(没有愚蠢的空方法的非黑客解决方案)是使用专门的DTO层进行通信(例如,只有带有默认公共构造函数的POJO),但在需要发送包含大量交叉引用的复杂对象图的情况下,这可能会很困难。

回答正确。我非常同意您的观点,即整个场景完全令人困惑。任何提供自定义字段序列化类的类都应自动添加到白名单对象中;当前的实现etty much迫使开发者要么破解问题,要么给他们的系统增加一层复杂性。我认为真相比这个答案所暗示的要简单一些。首先,
final
字段被忽略(以,因此,在发送其所有者类型时,无论自定义序列化程序做什么,都不符合通过线路发送的条件,因为它在理论上可以调用任何代码(即,停止问题很难解决可能的类型)。因此,添加引用该类型的新方法足以将其标记为包含。删除
final
(或修复1054),则不需要此方法。
interface MyService {

   Foo getFoo(Bar bar); 

}
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

}