Sollte ich Jacksons ObjectMapper als statisches Feld deklarieren?

361

Die ObjectMapperKlasse der Jackson-Bibliothek scheint threadsicher zu sein .

Bedeutet dies, dass ich mein ObjectMapperFeld als solches statisch deklarieren sollte?

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

statt als solches Feld auf Instanzebene?

class Me {
    private final ObjectMapper mapper = new ObjectMapper();
}
Cheok Yan Cheng
quelle

Antworten:

505

Ja, das ist sicher und empfohlen.

Die einzige Einschränkung auf der Seite, auf die Sie verwiesen haben, ist, dass Sie die Konfiguration des Mappers nicht mehr ändern können, sobald er freigegeben ist. Sie ändern jedoch nicht die Konfiguration, sodass dies in Ordnung ist. Wenn Sie die Konfiguration ändern müssten, würden Sie dies über den statischen Block tun, und es wäre auch in Ordnung.

EDIT : (2013/10)

Ab 2.0 kann das oben Gesagte erweitert werden, indem festgestellt wird, dass es einen noch besseren Weg gibt: Verwendung ObjectWriterund ObjectReaderObjekte, die von konstruiert werden können ObjectMapper. Sie sind vollständig unveränderlich, threadsicher, was bedeutet, dass es theoretisch nicht einmal möglich ist, Thread-Sicherheitsprobleme zu verursachen (die auftreten können, ObjectMapperwenn Code versucht, die Instanz neu zu konfigurieren).

StaxMan
quelle
23
@StaxMan: Ich bin etwas besorgt, ob ObjectMappernach dem ObjectMapper#setDateFormat()Aufruf noch threadsicher ist. Es ist bekannt, dass dies SimpleDateFormatnicht threadsicher ist und daher ObjectMappernicht möglich ist, es sei denn, es klont zB SerializationConfigvor jedem writeValue()(ich bezweifle). Könnten Sie meine Angst entlarven?
dma_k
49
DateFormatist in der Tat unter der Haube geklont. Guter Verdacht dort, aber Sie sind abgesichert. :)
StaxMan
3
Ich habe bei Unit- / Integrationstests einer großen Unternehmensanwendung seltsame Verhaltensweisen festgestellt. Als ich ObjectMapper als statisches Attribut für die letzte Klasse einsetzte, stellte ich fest, dass PermGen-Probleme auftraten. Würde es jemandem etwas ausmachen, mögliche Ursachen zu erklären? Ich habe Jackson-Databind Version 2.4.1 verwendet.
Alejo Ceballos
2
@MiklosKrivan haben Sie sah ObjectMapperüberhaupt ?! Methoden werden benannt writer()und reader()(und einige readerFor(), writerFor()).
StaxMan
2
Es gibt keinen mapper.with()Aufruf (da "with" in Jackson die Erstellung einer neuen Instanz und die thread-sichere Ausführung impliziert). Aber bezüglich Konfigurationsänderungen: Es wird keine Überprüfung durchgeführt, daher muss der Konfigurationszugriff auf ObjectMappergeschützt werden. Zu "copy ()": Ja, das erstellt eine neue Kopie, die nach denselben Regeln vollständig (neu) konfiguriert werden kann: zuerst vollständig konfigurieren, dann verwenden, und das ist in Ordnung. Es sind nicht triviale Kosten verbunden (da die Kopie keinen der zwischengespeicherten Handler verwenden kann), aber dies ist der sichere Weg, ja.
StaxMan
53

Obwohl ObjectMapper threadsicher ist, würde ich dringend davon abraten, es als statische Variable zu deklarieren, insbesondere in Multithread-Anwendungen. Nicht einmal, weil es eine schlechte Praxis ist, sondern weil Sie ein hohes Risiko eines Deadlocks haben. Ich erzähle es aus meiner eigenen Erfahrung. Ich habe eine Anwendung mit 4 identischen Threads erstellt, die JSON-Daten von Webdiensten abrufen und verarbeiten. Meine Anwendung wurde laut Thread-Dump häufig mit dem folgenden Befehl blockiert:

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

Außerdem war die Leistung nicht gut. Als ich die statische Variable durch die instanzbasierte Variable ersetzte, verschwand das Abwürgen und die Leistung vervierfachte sich. Dh 2,4 Millionen JSON-Dokumente wurden in 40 Minuten, 56 Sekunden anstatt 2,5 Stunden zuvor verarbeitet.

Gary Greenberg
quelle
15
Garys Antwort macht völlig Sinn. Das Erstellen einer ObjectMapperInstanz für jede Klasseninstanz kann jedoch Sperren verhindern, kann aber später die GC erheblich belasten (stellen Sie sich eine ObjectMapper-Instanz für jede Instanz der von Ihnen erstellten Klasse vor). Ein Ansatz mit mittlerem Pfad kann darin bestehen, dass Sie nicht nur eine (öffentliche) statische ObjectMapperInstanz in der Anwendung behalten, sondern in jeder Klasse eine (private) statische Instanz deklarieren können . Dies reduziert eine globale Sperre (durch Verteilung der Lastklasse) und erstellt auch kein neues Objekt, wodurch auch die GC beleuchtet wird. ObjectMapper
Abhidemon
Und natürlich ist die Aufrechterhaltung eines ObjectPool der beste Weg, den Sie gehen können - und damit das Beste GCund die besten LockLeistungen zu erzielen. Sie können auf den folgenden Link für die ObjectPoolImplementierung von Apache-Common verweisen . commons.apache.org/proper/commons-pool/api-1.6/org/apache/…
Abhidemon
16
Ich würde eine Alternative vorschlagen: ObjectMapperirgendwo statisch bleiben , aber nur ObjectReader/ ObjectWriterInstanzen abrufen (über Hilfsmethoden), Verweise auf solche an anderen Stellen beibehalten (oder dynamisch aufrufen). Diese Reader / Writer-Objekte sind nicht nur vollständig threadsicher für die Rekonfiguration, sondern auch sehr leicht (Wrt-Mapper-Instanzen). Das Speichern von Tausenden von Referenzen führt also nicht zu einer hohen Speichernutzung.
StaxMan
Wenn also Aufrufe von ObjectReader-Instanzen nicht blockiert werden, dh wenn objectReader.readTree in einer Multithread-Anwendung aufgerufen wird, werden Threads nicht blockiert, wenn sie auf einen anderen Thread warten, wobei Jackson 2.8.x
Xephonia am
1

Obwohl es sicher ist, einen statischen ObjectMapper im Hinblick auf die Thread-Sicherheit zu deklarieren, sollten Sie sich bewusst sein, dass das Erstellen statischer Objektvariablen in Java als schlechte Praxis angesehen wird. Weitere Informationen finden Sie unter Warum werden statische Variablen als böse angesehen? (und wenn Sie möchten, meine Antwort )

Kurz gesagt, Statik sollte vermieden werden, da dies das Schreiben präziser Komponententests erschwert. Beispielsweise können Sie mit einem statischen endgültigen ObjectMapper die JSON-Serialisierung nicht gegen Dummy-Code oder ein No-Op austauschen.

Darüber hinaus verhindert ein statisches Finale, dass Sie ObjectMapper zur Laufzeit neu konfigurieren können. Sie können sich jetzt vielleicht keinen Grund dafür vorstellen, aber wenn Sie sich in ein statisches endgültiges Muster einschließen, können Sie es durch Herunterfahren des Klassenladeprogramms neu initialisieren.

Im Fall von ObjectMapper ist es in Ordnung, aber im Allgemeinen ist es eine schlechte Praxis und es gibt keinen Vorteil gegenüber der Verwendung eines Singleton-Musters oder einer Umkehrung der Kontrolle, um Ihre langlebigen Objekte zu verwalten.

JBCP
quelle
27
Ich würde vorschlagen, dass statische STATEFUL-Singletons zwar normalerweise ein Gefahrenzeichen sind, es jedoch genügend Gründe gibt, warum in diesem Fall das Teilen einer einzelnen (oder einer kleinen Anzahl von) Instanzen sinnvoll ist. Möglicherweise möchten Sie dafür Dependency Injection verwenden. Gleichzeitig lohnt es sich jedoch zu fragen, ob ein tatsächliches oder ein potenzielles Problem zu lösen ist. Dies gilt insbesondere für Tests: Nur weil etwas in einigen Fällen problematisch sein kann, bedeutet dies nicht, dass es für Ihre Verwendung bestimmt ist. Also: sich der Probleme bewusst sein, großartig. Angenommen, "one size fits all", nicht so gut.
StaxMan
3
Natürlich ist es wichtig, die Probleme zu verstehen, die mit einer Entwurfsentscheidung verbunden sind, und wenn Sie etwas tun können, ohne Probleme für Ihren Anwendungsfall zu verursachen, werden Sie per Definition keine Probleme verursachen. Ich würde jedoch argumentieren, dass die Verwendung statischer Instanzen keine Vorteile bietet und die Tür für erhebliche Probleme in der Zukunft öffnet, wenn sich Ihr Code weiterentwickelt oder an andere Entwickler weitergegeben wird, die Ihre Entwurfsentscheidungen möglicherweise nicht verstehen. Wenn Ihr Framework Alternativen unterstützt, gibt es keinen Grund, statische Instanzen nicht zu vermeiden. Es gibt sicherlich keine Vorteile für sie.
JBCP
11
Ich denke, diese Diskussion geht auf sehr allgemeine und weniger nützliche Tangenten ein. Ich habe kein Problem damit, dass es gut ist, statischen Singletons gegenüber misstrauisch zu sein. Ich bin einfach sehr vertraut mit der Verwendung für diesen speziellen Fall und ich glaube nicht, dass man aus einer Reihe allgemeiner Richtlinien spezifische Schlussfolgerungen ziehen kann. Also werde ich es dabei belassen.
StaxMan
1
Später Kommentar, aber würde ObjectMapper dieser Vorstellung nicht besonders widersprechen? Es macht readerForund writerForwelche erstellen ObjectReaderund ObjectWriterInstanzen bei Bedarf. Also würde ich sagen, setzen Sie den Mapper mit der anfänglichen Konfiguration an einen statischen Ort und holen Sie sich dann Leser / Schreiber mit der Konfiguration pro Fall, wenn Sie sie benötigen?
Carighan
1

Ein Trick, den ich aus dieser PR gelernt habe, wenn Sie sie nicht als statische Endvariable definieren, sondern ein wenig Overhead sparen und Thread-Sicherheit gewährleisten möchten.

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

Gutschrift an den Autor.

Henry Lin
quelle
2
Es besteht jedoch die Gefahr eines Speicherverlusts, da ObjectMapperdieser an den Thread angehängt wird, der möglicherweise Teil eines Pools ist.
Kenston Choi
@ KenstonChoi Sollte kein Problem sein, AFAIU. Die Threads kommen und gehen, Thread-Einheimische kommen und gehen mit den Threads. Abhängig von der Anzahl der gleichzeitigen Threads können Sie sich den Speicher leisten oder nicht, aber ich sehe keine "Lecks".
Ivan
2
@IvanBalashov, aber wenn der Thread von / zu einem Thread-Pool (z. B. Containern wie Tomcat) erstellt / zurückgegeben wird, bleibt er erhalten. Dies kann in einigen Fällen erwünscht sein, aber etwas, das wir beachten müssen.
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)

Die Methode _hashMapSuperInterfaceChain in der Klasse com.fasterxml.jackson.databind.type.TypeFactory wird synchronisiert. Ich sehe Streit um das gleiche bei hohen Lasten.

Kann ein weiterer Grund sein, einen statischen ObjectMapper zu vermeiden

Harshit
quelle
1
Schauen Sie sich unbedingt die neuesten Versionen an (und geben Sie möglicherweise die Version an, die Sie hier verwenden). Das Sperren wurde aufgrund der gemeldeten Probleme verbessert, und die Typauflösung (z. B.) wurde für Jackson 2.7 vollständig neu geschrieben. Obwohl es in diesem Fall TypeReferenceetwas teuer ist, es zu verwenden: Wenn möglich, würde das Auflösen JavaTypeeiniges an Verarbeitung vermeiden ( TypeReferences können - leider - aus Gründen, auf die ich hier nicht näher eingehen werde, nicht zwischengespeichert werden), da sie sind "vollständig aufgelöst" (Supertyp, generische Typisierung usw.).
StaxMan