Devo dichiarare ObjectMapper di Jackson come un campo statico?


361

La ObjectMapperclasse della biblioteca di Jackson sembra essere thread-safe .

Questo significa che dovrei dichiarare il mio ObjectMappercampo statico come questo

class Me {
    private static final ObjectMapper mapper = new ObjectMapper();
}

anziché come un campo a livello di istanza come questo?

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}

Risposte:


505

Sì, è sicuro e consigliato.

L'unico avvertimento dalla pagina che hai indicato è che non puoi modificare la configurazione del mapper una volta condiviso; ma non stai modificando la configurazione, quindi va bene. Se fosse necessario modificare la configurazione, lo si farebbe dal blocco statico e andrebbe anche bene.

EDIT : (2013/10)

Con 2.0 e versioni successive, le versioni precedenti possono essere aumentate osservando che esiste un modo ancora migliore: uso ObjectWritere ObjectReaderoggetti, che possono essere costruiti da ObjectMapper. Sono completamente immutabili, sicuri per i thread, il che significa che teoricamente non è nemmeno possibile causare problemi di sicurezza dei thread (che possono verificarsi ObjectMapperse il codice tenta di riconfigurare l'istanza).


23
@StaxMan: Sono un po 'preoccupato se ObjectMapperdopo il ObjectMapper#setDateFormat()richiamo è ancora thread-safe . È noto che SimpleDateFormatnon è thread-safe , quindi ObjectMappernon lo sarà a meno che non cloni ad esempio SerializationConfigprima di ciascuno writeValue()(ne dubito). Potresti ridimensionare la mia paura?
dma_k,

49
DateFormatè infatti clonato sotto il cofano. Buon sospetto lì, ma sei coperto. :)
StaxMan

3
Ho incontrato comportamenti strani durante i test di unità / integrazione di una grande applicazione aziendale. Quando ho inserito ObjectMapper come attributo di classe finale statico ho iniziato ad affrontare problemi con PermGen. Qualcuno vorrebbe spiegare le probabili cause? Stavo usando la versione 2.4.1 di jackson-databind.
Alejo Ceballos,

2
@MiklosKrivan hai visto ObjectMappertutto ?! I metodi sono nominati writer()e reader()(e alcuni readerFor(), writerFor()).
StaxMan,

2
Non vi è alcuna mapper.with()chiamata (poiché "con" a Jackson implica la costruzione di una nuova istanza e l'esecuzione thread-safe). Ma per quanto riguarda le modifiche alla configurazione: non viene effettuato alcun controllo, quindi l'accesso alla configurazione ObjectMapperdeve essere protetto. Per quanto riguarda "copia ()": sì, crea una nuova copia nuova che può essere completamente (ri) configurata, secondo le stesse regole: configurala prima per prima, quindi usa e va bene. Sono associati costi non banali (poiché la copia non può utilizzare nessuno dei gestori memorizzati nella cache), ma è il modo sicuro, sì.
StaxMan,

53

Sebbene ObjectMapper sia thread-safe, scoraggerei fortemente dal dichiararlo come variabile statica, specialmente in applicazioni multithread. Neanche perché è una cattiva pratica, ma perché stai correndo un grosso rischio di deadlock. Lo dico per esperienza personale. Ho creato un'applicazione con 4 thread identici che stavano ottenendo ed elaborando i dati JSON dai servizi web. La mia applicazione era spesso bloccata sul seguente comando, secondo il dump del thread:

Map aPage = mapper.readValue(reader, Map.class);

Inoltre, le prestazioni non erano buone. Quando ho sostituito la variabile statica con la variabile basata sull'istanza, lo stallo è scomparso e le prestazioni sono quadruplicate. Vale a dire 2,4 milioni di documenti JSON sono stati elaborati in 40 minuti e 56 secondi, anziché 2,5 ore prima.


15
La risposta di Gary ha completamente senso. Ma andare con la creazione di ObjectMapperun'istanza per ogni istanza della classe può impedire i blocchi, ma può essere molto pesante su GC in seguito (immagina un'istanza di ObjectMapper per ogni istanza della classe che crei). Un approccio a percorso intermedio può essere, invece di mantenere solo ObjectMapperun'istanza (pubblica) statica nell'applicazione, è possibile dichiarare un'istanza statica (privata) ObjectMapper in ogni classe . Ciò ridurrà un blocco globale (distribuendo il carico in termini di classe) e non creerà alcun nuovo oggetto, quindi anche la luce su GC.
Abhidemon,

E, naturalmente, mantenere un ObjectPool è il modo migliore con cui puoi andare, dando così il meglio GCe le Lockprestazioni. Puoi fare riferimento al seguente link per l' ObjectPoolimplementazione di apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon

16
Suggerirei un'alternativa: mantieni statico ObjectMapperda qualche parte, ma ottieni solo ObjectReader/ ObjectWriterinstance (tramite metodi helper), mantieni i riferimenti a quelli in altri posti (o chiama dinamicamente). Questi oggetti reader / writer non sono solo riconfigurazione wrt completamente thread-safe, ma anche molto leggera (istanze di wrt mapper). Quindi mantenere migliaia di riferimenti non aggiunge molto utilizzo della memoria.
StaxMan,

Quindi la chiamata alle istanze di ObjectReader non si blocca, ad esempio dire che objectReader.readTree viene chiamato in un'applicazione multithread, i thread non verranno bloccati in attesa su un altro thread, utilizzando jackson 2.8.x
Xephonia

1

Sebbene sia sicuro dichiarare un ObjectMapper statico in termini di sicurezza dei thread, è necessario tenere presente che la costruzione di variabili statiche di oggetti in Java è considerata una cattiva pratica. Per maggiori dettagli, vedi Perché le variabili statiche sono considerate malvagie? (e se vuoi, la mia risposta )

In breve, la statica dovrebbe essere evitata perché rende difficile scrivere test unitari concisi. Ad esempio, con un ObjectMapper finale statico, non è possibile scambiare la serializzazione JSON con codice fittizio o no-op.

Inoltre, un finale statico ti impedisce di riconfigurare ObjectMapper in fase di esecuzione. Potresti non immaginare un motivo per questo ora, ma se ti blocchi in uno schema finale statico, nientemeno che abbattere il classloader ti permetterà di reinizializzarlo.

Nel caso di ObjectMapper va bene, ma in generale è una cattiva pratica e non vi è alcun vantaggio rispetto all'utilizzo di un modello singleton o inversione di controllo per gestire i tuoi oggetti di lunga durata.


27
Vorrei suggerire che sebbene i singleton STATEFUL statici siano in genere un segnale di pericolo, ci sono abbastanza ragioni per cui in questo caso ha senso condividere un singolo (o, piccolo numero di) istanze. Uno potrebbe voler usare l'iniezione di dipendenza per quello; ma allo stesso tempo vale la pena chiedere se esiste un problema reale o potenziale da risolvere. Questo vale soprattutto per i test: solo perché qualcosa potrebbe essere problematico in alcuni casi non significa che sia per il tuo utilizzo. Quindi: essere consapevoli dei problemi, fantastico. Supponendo che "taglia unica", non così buono.
StaxMan

3
Ovviamente è importante comprendere i problemi legati a qualsiasi decisione di progettazione, e se puoi fare qualcosa senza causare problemi al tuo utilizzo, per definizione non causerai problemi. Tuttavia, direi che non ci sono vantaggi nell'uso di istanze statiche e apre le porte a problemi significativi in ​​futuro con l'evoluzione del codice o la consegna ad altri sviluppatori che potrebbero non capire le tue decisioni di progettazione. Se il tuo framework supporta alternative, non c'è motivo per non evitare istanze statiche, non ci sono certamente vantaggi.
JBCP

11
Penso che questa discussione entri in tangenti molto generali e meno utili. Non ho problemi a suggerire che è bene essere diffidenti nei confronti dei singoli statici. Mi è solo molto familiare per l'uso in questo caso particolare e non credo che si possano trarre conclusioni specifiche da una serie di linee guida generali. Quindi lo lascerò a quello.
StaxMan

1
Commento tardivo, ma ObjectMapper non sarebbe in particolare in disaccordo con questa nozione? Espone readerFore writerForquali creano ObjectReadere ObjectWriteristanze su richiesta. Quindi direi di mettere il mapper con la configurazione iniziale da qualche parte statica, quindi ottenere lettori / scrittori con la configurazione per caso di cui hai bisogno?
Carighan,

1

Un trucco che ho imparato da questo PR se non vuoi definirlo come variabile finale statica ma vuoi risparmiare un po 'di overhead e garantire thread thread safe.

private static final ThreadLocal<ObjectMapper> om = new ThreadLocal<ObjectMapper>() {
    @Override
    protected ObjectMapper initialValue() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        return objectMapper;
    }
};

public static ObjectMapper getObjectMapper() {
    return om.get();
}

merito all'autore.


2
Ma c'è il rischio di una perdita di memoria poiché ObjectMapperverrà collegata al thread che potrebbe far parte di un pool.
Kenston Choi,

@KenstonChoi Non dovrebbe essere un problema, AFAIU. I fili vanno e vengono, i locali del filo vanno e vengono con i fili. A seconda della quantità di thread simultanei potresti non permetterti la memoria, ma non vedo "perdite".
Ivan Balashov,

2
@IvanBalashov, ma se il thread viene creato / restituito da / a un pool di thread (ad esempio, contenitori come Tomcat), rimane. Questo può essere desiderato in alcuni casi, ma qualcosa di cui dobbiamo essere consapevoli.
Kenston Choi,

-1

com.fasterxml.jackson.databind.type.TypeFactory._hashMapSuperInterfaceChain (HierarchicType)

com.fasterxml.jackson.databind.type.TypeFactory._findSuperInterfaceChain(Type, Class)
  com.fasterxml.jackson.databind.type.TypeFactory._findSuperTypeChain(Class, Class)
     com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(Class, Class, TypeBindings)
        com.fasterxml.jackson.databind.type.TypeFactory.findTypeParameters(JavaType, Class)
           com.fasterxml.jackson.databind.type.TypeFactory._fromParamType(ParameterizedType, TypeBindings)
              com.fasterxml.jackson.databind.type.TypeFactory._constructType(Type, TypeBindings)
                 com.fasterxml.jackson.databind.type.TypeFactory.constructType(TypeReference)
                    com.fasterxml.jackson.databind.ObjectMapper.convertValue(Object, TypeReference)

Il metodo _hashMapSuperInterfaceChain nella classe com.fasterxml.jackson.databind.type.TypeFactory è sincronizzato. Sto vedendo contesa sullo stesso a carichi elevati.

Potrebbe essere un altro motivo per evitare un ObjectMapper statico


1
Assicurati di controllare le ultime versioni (e forse indicare la versione che usi qui). Sono stati apportati miglioramenti al blocco in base ai problemi segnalati e la risoluzione del tipo (f.ex) è stata completamente riscritta per Jackson 2.7. Anche se in questo caso, TypeReferenceè una cosa un po 'costosa da usare: se possibile, risolverlo JavaTypeeviterebbe un bel po' di elaborazione ( TypeReferencenon si può - purtroppo - essere memorizzati nella cache per motivi che non approfondirò qui), dal momento che sono "completamente risolti" (tipo super, tipizzazione generica ecc.).
StaxMan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.