Dois-je déclarer l'ObjectMapper de Jackson comme champ statique?

361

La ObjectMapperclasse de la bibliothèque Jackson semble être thread-safe .

Est-ce que cela signifie que je devrais déclarer mon ObjectMapperchamp statique comme celui-ci

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

au lieu d'un champ de niveau instance comme celui-ci?

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

Réponses:

505

Oui, c'est sûr et recommandé.

La seule mise en garde de la page que vous avez mentionnée est que vous ne pouvez pas modifier la configuration du mappeur une fois qu'il est partagé; mais vous ne changez pas la configuration, donc ça va. Si vous aviez besoin de changer la configuration, vous le feriez à partir du bloc statique et ce serait bien aussi.

EDIT : (2013/10)

Avec 2.0 et supérieur, ce qui précède peut être augmenté en notant qu'il existe un meilleur moyen: l'utilisation ObjectWriteret les ObjectReaderobjets, qui peuvent être construits par ObjectMapper. Ils sont entièrement immuables, thread-safe, ce qui signifie qu'il n'est même pas théoriquement possible de provoquer des problèmes de sécurité des threads (qui peuvent survenir ObjectMappersi le code tente de reconfigurer l'instance).

StaxMan
la source
23
@StaxMan: Je suis un peu inquiet si le ObjectMapperthread-safe ObjectMapper#setDateFormat()est toujours appelé. Il est connu que ce SimpleDateFormatn'est pas sûr pour les threads , donc ObjectMapperil ne le sera pas sauf s'il clone par exemple SerializationConfigavant chaque writeValue()(je doute). Pourriez-vous démystifier ma peur?
dma_k
49
DateFormatest en effet cloné sous le capot. Bon soupçon là-bas, mais vous êtes couvert. :)
StaxMan
3
J'ai rencontré des comportements étranges lors des tests unitaires / d'intégration d'une grande application d'entreprise. En mettant ObjectMapper comme attribut de classe finale statique, j'ai commencé à faire face à des problèmes PermGen. Quelqu'un voudrait-il expliquer les causes probables? J'utilisais jackson-databind version 2.4.1.
Alejo Ceballos
2
@MiklosKrivan avez-vous regardé ObjectMapperdu tout?! Les méthodes sont nommées writer()et reader()(et certaines readerFor(), writerFor()).
StaxMan
2
Il n'y a pas d' mapper.with()appel (car "avec" dans Jackson implique la construction d'une nouvelle instance et une exécution thread-safe). Mais en ce qui concerne les changements de configuration: aucune vérification n'est effectuée, donc l'accès à la configuration ObjectMapperdoit être gardé. Quant à "copy ()": oui, cela crée une nouvelle copie qui peut être entièrement (re) configurée, selon les mêmes règles: configurez-la entièrement en premier, puis utilisez, et c'est très bien. Il y a un coût non trivial associé (car la copie ne peut utiliser aucun des gestionnaires mis en cache), mais c'est le moyen sûr, oui.
StaxMan
53

Bien que ObjectMapper soit sûr pour les threads, je déconseille fortement de le déclarer comme variable statique, en particulier dans les applications multithread. Pas même parce que c'est une mauvaise pratique, mais parce que vous courez un gros risque de blocage. Je le raconte de ma propre expérience. J'ai créé une application avec 4 threads identiques qui récupéraient et traitaient les données JSON des services Web. Mon application bloquait fréquemment la commande suivante, selon le vidage de thread:

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

En plus de cela, les performances n'étaient pas bonnes. Lorsque j'ai remplacé la variable statique par la variable basée sur l'instance, le blocage a disparu et les performances ont quadruplé. Soit 2,4 millions de documents JSON ont été traités en 40 minutes 56 secondes, au lieu de 2,5 heures auparavant.

Gary Greenberg
la source
15
La réponse de Gary est parfaitement logique. Mais le fait de créer une ObjectMapperinstance pour chaque instance de classe peut empêcher les verrous mais peut être très lourd sur GC plus tard (imaginez une instance ObjectMapper pour chaque instance de la classe que vous créez). Une approche intermédiaire peut être, au lieu de conserver une seule ObjectMapperinstance statique (publique) dans l'application, vous pouvez déclarer une instance statique (privée) de ObjectMapper dans chaque classe . Cela réduira un verrouillage global (en répartissant la charge par classe) et ne créera pas non plus de nouvel objet, donc la lumière sur GC aussi.
Abhidemon
Et bien sûr, le maintien d'un ObjectPool est la meilleure façon de procéder - donnant ainsi le meilleur GCet les Lockperformances. Vous pouvez vous référer au lien suivant pour l' ObjectPoolimplémentation d' apache-common . commons.apache.org/proper/commons-pool/api-1.6/org/apache/...
Abhidemon
16
Je suggérerais une alternative: garder statique ObjectMapperquelque part, mais seulement obtenir ObjectReader/ ObjectWriterinstances (via des méthodes d'assistance), conserver les références à celles d'autres endroits (ou appeler dynamiquement). Ces objets de lecture / écriture sont non seulement une reconfiguration wrt entièrement thread-safe, mais aussi très légers (instances de mappeur wrt). Ainsi, conserver des milliers de références n'ajoute pas beaucoup d'utilisation de la mémoire.
StaxMan
Les appels aux instances ObjectReader ne sont donc pas bloquants, c'est-à-dire que objectReader.readTree est appelé dans une application multithread, les threads ne seront pas bloqués en attente sur un autre thread, en utilisant jackson 2.8.x
Xephonia
1

Bien qu'il soit sûr de déclarer un ObjectMapper statique en termes de sécurité des threads, vous devez savoir que la construction de variables Object statiques en Java est considérée comme une mauvaise pratique. Pour plus de détails, voir Pourquoi les variables statiques sont-elles considérées comme mauvaises? (et si vous le souhaitez, ma réponse )

En bref, la statique doit être évitée car elle rend difficile la rédaction de tests unitaires concis. Par exemple, avec un ObjectMapper final statique, vous ne pouvez pas échanger la sérialisation JSON contre du code factice ou un no-op.

De plus, une finale statique vous empêche de reconfigurer ObjectMapper lors de l'exécution. Vous n'envisagez peut-être pas une raison à cela maintenant, mais si vous vous enfermez dans un modèle final statique, rien de moins que de détruire le chargeur de classe vous permettra de le réinitialiser.

Dans le cas d'ObjectMapper, c'est bien, mais en général c'est une mauvaise pratique et il n'y a aucun avantage à utiliser un modèle singleton ou une inversion de contrôle pour gérer vos objets à longue durée de vie.

JBCP
la source
27
Je dirais que bien que les singletons statiques statiques soient généralement un signe de danger, il y a suffisamment de raisons pour lesquelles, dans ce cas, le partage d'une seule (ou d'un petit nombre) d'instances est logique. On peut vouloir utiliser l'injection de dépendance pour cela; mais en même temps, il vaut la peine de se demander s'il existe un problème réel ou potentiel à résoudre. Cela s'applique particulièrement aux tests: ce n'est pas parce que quelque chose peut être problématique dans certains cas que c'est pour votre usage. Donc: être conscient des problèmes, tant mieux. En supposant "taille unique", pas si bon.
StaxMan
3
De toute évidence, il est important de comprendre les problèmes liés à toute décision de conception, et si vous pouvez faire quelque chose sans causer de problèmes pour votre cas d'utilisation, vous ne causerez par définition aucun problème. Cependant, je dirais qu'il n'y a aucun avantage à utiliser des instances statiques et cela ouvre la porte à des problèmes importants à l'avenir à mesure que votre code évolue ou est remis à d'autres développeurs qui pourraient ne pas comprendre vos décisions de conception. Si votre framework prend en charge des alternatives, il n'y a aucune raison de ne pas éviter les instances statiques, elles ne présentent certainement aucun avantage.
JBCP
11
Je pense que cette discussion va dans des tangentes très générales et moins utiles. Je n'ai aucun problème à suggérer qu'il est bon de se méfier des singletons statiques. Il se trouve que je suis très familier pour l'utilisation dans ce cas particulier et je ne pense pas que l'on puisse tirer des conclusions spécifiques d'un ensemble de directives générales. Je vais donc en rester là.
StaxMan
1
Commentaire tardif, mais ObjectMapper en particulier ne serait-il pas en désaccord avec cette notion? Il expose readerForet writerForcrée ObjectReaderet ObjectWriterinstalle à la demande. Je dirais donc de mettre le mappeur avec la configuration initiale quelque part statique, puis d'obtenir des lecteurs / écrivains avec une configuration par cas selon vos besoins?
Carighan
1

Une astuce que j'ai apprise de ce PR si vous ne voulez pas la définir comme une variable finale statique mais que vous voulez économiser un peu de surcharge et garantir la sécurité des threads.

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

crédit à l'auteur.

Henry Lin
la source
2
Mais il y a un risque de fuite de mémoire car le ObjectMappersera attaché au thread qui peut faire partie d'un pool.
Kenston Choi
@KenstonChoi Cela ne devrait pas être un problème, AFAIU. Les fils vont et viennent, les habitants du fil vont et viennent avec les fils. Selon la quantité de threads simultanés, vous pouvez ou non vous permettre la mémoire, mais je ne vois pas de "fuites".
Ivan Balashov
2
@IvanBalashov, mais si le thread est créé / renvoyé depuis / vers un pool de threads (par exemple, des conteneurs comme Tomcat), il reste. Cela peut être souhaité dans certains cas, mais nous devons en être conscients.
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)

La méthode _hashMapSuperInterfaceChain dans la classe com.fasterxml.jackson.databind.type.TypeFactory est synchronisée. Je vois la contention sur le même à des charges élevées.

Peut être une autre raison d'éviter un ObjectMapper statique

Harshit
la source
1
Assurez-vous de consulter les dernières versions (et peut-être indiquer la version que vous utilisez ici). Des améliorations ont été apportées au verrouillage en fonction des problèmes signalés et la résolution de type (f.ex) a été entièrement réécrite pour Jackson 2.7. Bien que dans ce cas, TypeReferencec'est une chose un peu chère à utiliser: si possible, le résoudre JavaTypeéviterait pas mal de traitement (les TypeReferences ne peuvent pas - malheureusement - être mis en cache pour des raisons que je ne vais pas approfondir ici), car ils sont "entièrement résolus" (super-type, typage générique, etc.).
StaxMan