2013-02-25 4 views
6

Considerare una classe immutabile Foo (un POJO costituito da un ID e un nome), che deve essere serializzata in modo che i dati vengano inviati dal server al client.Edizione di serializzazione campo personalizzata GWT

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. 
    . . . 
} 

di istanze di un oggetto Foo avviene attraverso la funzione di fabbrica statica e poiché non v'è alcun costruttore zero args immutabile dell'oggetto.

Considera anche una barra di classe immutabile che contiene un membro dati Foo e implementa il modello Builder per la sua creazione di istanze (omesso dallo snippet poiché è irrilevante per il problema).

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

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

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

Dopo aver valutato le mie scelte per quanto riguarda il modo in cui devono serializzare l'oggetto senza rimuovere la sua immutabilità aggiungendo o togliendo le zero-args costruttori per il bene di serializzazione ho concluso al fatto che avevo bisogno di implementare un CustomFieldSerializer (per entrambi il client e il server).

Ho seguito le linee guida scritte nell'articolo Server Communication di GWT e ho implementato il mio CustomFieldSerializer come illustrato di seguito.

// 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); 
    } 
} 

Dal momento che la barra di classe contiene un oggetto Foo che deve essere serializzato sono andato su e realizzato un altro set di CustomFieldSerializers, questa volta per la classe Foo seguendo lo stesso schema, sia per il client e il server.

Il problema sorge quando si verifica la serializzazione per la classe bar e, in particolare, a questo punto:

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

Il messaggio di eccezione che ottengo è il seguente:

[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. 

Sembra che writeObject() non può serializzare l'oggetto di tipo Foo perché la classe Foo non appartiene agli elementi autorizzati, anche se sono stati forniti serializzatori personalizzati sia per il client che per il server.

ho sempre potuto saltare il writeObject() invocazione e chiamare writeInt() & WriteString() per i membri di dati di ogni Foo (che sta lavorando bene), ma io preferirei di gran lunga avere writeObject() lavorare. La soluzione che ho proposto è altamente soggetta a errori di manutenzione poiché qualsiasi modifica nella classe Foo in futuro deve riflettersi sia sui serializzatori di Foo (ovvio) che sui serializzatori di Bar (non così ovvio).

ho provato praticamente tutto quello che ho trovato in rete, di attuare il interfaccia IsSerializable su entrambi i Foo e Bar (non ha fatto alcuna differenza e non dovrebbe fare alcuna differenza in quanto le classi AFAIK che forniscono la loro i propri serializzatori personalizzati non devono rispettare questa regola) e persino fornire un costruttore privato di zero-arg (che non dovrebbe fare alcuna differenza in quanto le funzioni di istanziazione del serializzatore del campo personalizzato dovrebbero occuparsene attraverso le fabbriche statiche).

Perché la classe Foo non è autorizzata? Ho perso qualcosa di ovvio o ho interpretato male qualcosa?

Grazie per il vostro tempo in anticipo.

risposta

6

Bene, il problema qui è che molto probabilmente non si cita esplicitamente da nessuna parte nel codice, che la classe Foo viene mai inviata al server. Per esempio. si hanno solo metodi di servizio come questo:

interface MyService { 

    Foo getFoo(Bar bar); 

} 

Per utilizzare writeObject è necessario fare in modo esplicito un suggerimento per GWT, per includere Foo classe in lista deserializzazione, con l'aggiunta di un nuovo metodo di servizio, che prende Foo classe come parametro:

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 

} 

Non è necessario chiamare questo metodo nell'app o fornire alcuna implementazione. Ma altrimenti non funzionerà. Ci sono pochi altri modi su come creare questo suggerimento per GWT, ma l'idea è la stessa, esporrà esplicitamente la classe Foo per GWT-RPC. Ricorda inoltre, che se si utilizza Bar di classe in più servizi, si dovrà aggiungere tale metodo ad ogni servizio che sta utilizzando Bar classe

Ora, maggiori dettagli perché questo accade. Sul lato server, GWT-RPC tiene traccia di due elenchi: oggetti che può serializzare e oggetti che può deserializzare. Queste informazioni sono prese da manifest delle politiche RPC. Nel mio primo esempio, ho solo menzionato l'oggetto Bar come qualcosa che può essere inviato al server. Ma poiché hai definito un serializzatore di campo personalizzato nella classe Bar, GWT non esegue alcuna analisi su Bar, quindi non ha idea che l'istanza di Foo possa essere inviata al server, quindi decide che non è necessario deserializzatore per il server Foo sul server lato. Quindi, quando provi a inviare l'istanza di Bar via cavo, il server prova a deserializzare, ma dal momento che lo readObject viene utilizzato all'interno del serializzatore personalizzato, prova a trovare anche il deserializzatore per Foo, ma non è consentito e quindi l'intero processo di deserealizzazione fallire. Se si aggiunge un ulteriore metodo di servizio, che invia solo l'oggetto Foo sul filo, GWT si rende conto che tale oggetto può essere inviato anche al server, pertanto contrassegna anche Foo come deserializzabile.

Questo molto confuso, e personalmente credo che le classi con serializzazione personalizzata dovrebbero essere aggiunte automaticamente a tutte le whitelist, ma è quello che è.

EDIT

Altro (soluzione non hacky senza metodi vuoti stupidi), sarà quello di utilizzare strato specializzata DTO per la comunicazione (ad esempio solo POJOs con predefinite costruttori pubblici), ma potrebbe essere difficile nei casi in cui si è necessario inviare grafici di oggetti complessi con molti riferimenti incrociati.

+0

Punto di risposta. Sono assolutamente d'accordo con te sul fatto che l'intero scenario sia completamente confuso. Qualsiasi classe che fornisca classi di serializzazione del campo personalizzate dovrebbe essere aggiunta automaticamente agli oggetti autorizzati; l'attuale implementazione impone agli sviluppatori di hackerare facilmente il problema o aggiungere un ulteriore livello di complessità al proprio sistema. –

+2

Penso che la verità sia leggermente più semplice di questa risposta suggerisce. Innanzitutto, i campi 'final' vengono ignorati (archiviati come http://code.google.com/p/google-web-toolkit/issues/detail?id=1054), e quindi non possono essere inviati sul filo quando il suo tipo di proprietario viene inviato, indipendentemente dal serializzatore personalizzato, dal momento che in teoria potrebbe chiamare qualsiasi codice (cioè è un problema di blocco difficile per risolvere i possibili tipi). Di conseguenza, aggiungere un nuovo metodo riferito a quel tipo è sufficiente per contrassegnarlo per l'inclusione. Rimuovi il 'final' (o ottieni 1054 riparato) e questo metodo non è necessario. –

+0

@ColinAlworth in realtà hai ragione al 100% – jusio