JAXB création de contexte et coût des marshallers

120

La question est un peu théorique, quel est le coût de création de contexte JAXB, marshaller et unmarshaller?

J'ai trouvé que mon code pourrait bénéficier de garder le même contexte JAXB et peut-être le même marshaller pour toutes les opérations de marshaling plutôt que de créer un contexte et un marshaller à chaque marshaling.

Alors, quel est le coût de la création d'un contexte JAXB et d'un marshaller / unmarshaller? Est-il correct de créer context + marshaller pour chaque opération de marshaling ou mieux vaut l'éviter?

Vladimir
la source

Réponses:

244

Remarque: je suis le responsable EclipseLink JAXB (MOXy) et membre du groupe d'experts JAXB 2 ( JSR-222 ).

JAXBContextest thread-safe et ne doit être créé qu'une seule fois et réutilisé pour éviter le coût d'initialisation des métadonnées plusieurs fois. Marshalleret Unmarshallerne sont pas thread-safe, mais sont légers à créer et peuvent être créés par opération.

bdoughan
la source
7
très bonne réponse. Je peux être confiant maintenant sur la base de votre expérience en tant que chef de file sur JAXB.
Vladimir
7
Je vous fais confiance, mais est-ce que cela se trouve quelque part dans la documentation?
Hurda
3
Il est documenté pour le RI: jaxb.java.net/guide/Performance_and_thread_safety.html (mais pas Moxy AFAIK)
Caoilte
39
Veuillez le spécifier dans la Javadoc. Il n'est pas satisfaisant que ces aspects cruciaux ne soient pas documentés.
Thomas W
6
Il n'est pas mentionné dans le Javadoc que JAXBContext est thread-safe. Et à peu près tous les exemples d'utilisation de JAXB ne mentionnent pas qu'il devrait être créé une fois et partagé entre les threads. En conséquence, je supprime maintenant une autre fuite de ressources hurlantes d'un système en direct. Grrrrrrr.
Reg Whitton
42

Idéalement, vous devriez avoir un singleton JAXBContextet des instances locales de Marshalleret Unmarshaller.

JAXBContextles instances sont thread-safe tandis Marshallerque les Unmarshallerinstances ne sont pas thread-safe et ne doivent jamais être partagées entre les threads.

Sahil Muthoo
la source
Merci d'avoir répondu. Malheureusement, je ne dois sélectionner qu'une seule réponse :-)
Vladimir
15

C'est dommage que cela ne soit pas spécifiquement décrit dans le javadoc. Ce que je peux dire, c'est que Spring utilise un JAXBContext global, partagé entre les threads, alors qu'il crée un nouveau marshaller pour chaque opération de marshalling, avec un commentaire javadoc dans le code indiquant que les marshallers JAXB ne sont pas nécessairement thread-safe.

La même chose est dite sur cette page: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Je suppose que la création d'un JAXBContext est une opération coûteuse, car elle implique l'analyse des classes et des packages pour les annotations. Mais la mesurer est la meilleure façon de le savoir.

JB Nizet
la source
Salut @JB, excellente réponse en particulier à vos commentaires sur la mesure et pourquoi JAXBContext est coûteux.
Vladimir
1
Javadoc a toujours été faible sur les faits cruciaux du cycle de vie . Cela nous donne certainement une répétition triviale des getters et des setters de propriété, mais quant à savoir comment / où obtenir ou créer une instance, la mutation et la sécurité des threads ... il semble manquer complètement ces facteurs les plus importants. Sigh :)
Thomas W
4

JAXB 2.2 ( JSR-222 ) a ceci à dire, dans la section "4.2 JAXBContext":

Pour éviter la surcharge liée à la création d'une JAXBContextinstance, une application JAXB est encouragée à réutiliser une instance JAXBContext . Une implémentation de la classe abstraite JAXBContext doit être thread-safe , par conséquent, plusieurs threads dans une application peuvent partager la même instance JAXBContext.

[..]

La classe JAXBContext est conçue pour être immuable et donc threadsafe. Étant donné la quantité de traitement dynamique qui pourrait potentiellement avoir lieu lors de la création d'une nouvelle instance de JAXBContxt, il est recommandé qu'une instance JAXBContext soit partagée entre les threads et réutilisée autant que possible pour améliorer les performances de l'application.

Malheureusement, la spécification ne fait aucune déclaration concernant la sécurité des threads de Unmarshalleret Marshaller. Il est donc préférable de supposer qu'ils ne le sont pas.

Martin Andersson
la source
3

J'ai résolu ce problème en utilisant:

  • threads partagés JAXBContext et thread local un / marschallers
  • (donc théoriquement, il y aura autant d' instances un / marshaller que de threads qui y accèdent)
  • avec synchronisation uniquement à l' initialisation de un / marshaller .
public class MyClassConstructor {
    private final ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<Unmarshaller>() {
        protected synchronized Unmarshaller initialValue() {
            try {
                return jaxbContext.createUnmarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create unmarshaller");
            }
        }
    };
    private final ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<Marshaller>() {
        protected synchronized Marshaller initialValue() {
            try {
                return jaxbContext.createMarshaller();
            } catch (JAXBException e) {
                throw new IllegalStateException("Unable to create marshaller");
            }
        }
    };

    private final JAXBContext jaxbContext;

    private MyClassConstructor(){
        try {
            jaxbContext = JAXBContext.newInstance(Entity.class);
        } catch (JAXBException e) {
            throw new IllegalStateException("Unable to initialize");
        }
    }
}
peeeto
la source
8
ThreadLocal introduira d'autres problèmes subtils, sans avantage. Gardez simplement un seul JAXBContext (c'est la partie coûteuse) et créez un nouveau Unmarshaller chaque fois que nécessaire.
ymajoros
2

Encore mieux!! En vous basant sur la bonne solution de l'article ci-dessus, créez le contexte une seule fois dans le constructeur et enregistrez-le à la place de la classe.

Remplacez la ligne:

  private Class clazz;

avec celui-ci:

  private JAXBContext jc;

Et le constructeur principal avec celui-ci:

  private Jaxb(Class clazz)
  {
     this.jc = JAXBContext.newInstance(clazz);
  }

donc dans le getMarshaller / getUnmarshaller, vous pouvez supprimer cette ligne:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Cette amélioration fait, dans mon cas, que les temps de traitement passent de 60 ~ 70ms à seulement 5 ~ 10ms

tbarderas
la source
Quelle était la taille du fichier xml que vous analysiez. Voyez-vous une amélioration significative avec les fichiers XML très volumineux?
John
1
ce n'est pas vraiment une question de gros fichiers xml (les mines vont de seulement 2-3kb à + 6mb), mais plutôt une question d'un très grand nombre de fichiers xml (on parle ici d'environ 10 000 requêtes xml par minute); dans ce cas, créer le contexte juste une fois gagner ces petites ms fait une énorme différence
tbarderas
1

Je résous généralement des problèmes comme celui-ci avec un ThreadLocalmodèle de classe. Étant donné que vous avez besoin d'un marshaller différent pour chaque classe, vous pouvez le combiner avec un singletonmodèle -map.

Pour vous faire gagner 15 minutes de travail. Voici mon implémentation d'une usine thread-safe pour Jaxb Marshallers et Unmarshallers.

Il vous permet d'accéder aux instances comme suit ...

Marshaller m = Jaxb.get(SomeClass.class).getMarshaller();
Unmarshaller um = Jaxb.get(SomeClass.class).getUnmarshaller();

Et le code dont vous aurez besoin est une petite classe Jaxb qui ressemble à ceci:

public class Jaxb
{
  // singleton pattern: one instance per class.
  private static Map<Class,Jaxb> singletonMap = new HashMap<>();
  private Class clazz;

  // thread-local pattern: one marshaller/unmarshaller instance per thread
  private ThreadLocal<Marshaller> marshallerThreadLocal = new ThreadLocal<>();
  private ThreadLocal<Unmarshaller> unmarshallerThreadLocal = new ThreadLocal<>();

  // The static singleton getter needs to be thread-safe too, 
  // so this method is marked as synchronized.
  public static synchronized Jaxb get(Class clazz)
  {
    Jaxb jaxb =  singletonMap.get(clazz);
    if (jaxb == null)
    {
      jaxb = new Jaxb(clazz);
      singletonMap.put(clazz, jaxb);
    }
    return jaxb;
  }

  // the constructor needs to be private, 
  // because all instances need to be created with the get method.
  private Jaxb(Class clazz)
  {
     this.clazz = clazz;
  }

  /**
   * Gets/Creates a marshaller (thread-safe)
   * @throws JAXBException
   */
  public Marshaller getMarshaller() throws JAXBException
  {
    Marshaller m = marshallerThreadLocal.get();
    if (m == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      m = jc.createMarshaller();
      marshallerThreadLocal.set(m);
    }
    return m;
  }

  /**
   * Gets/Creates an unmarshaller (thread-safe)
   * @throws JAXBException
   */
  public Unmarshaller getUnmarshaller() throws JAXBException
  {
    Unmarshaller um = unmarshallerThreadLocal.get();
    if (um == null)
    {
      JAXBContext jc = JAXBContext.newInstance(clazz);
      um = jc.createUnmarshaller();
      unmarshallerThreadLocal.set(um);
    }
    return um;
  }
}
bvdb
la source
10
ThreadLocal introduira d'autres problèmes subtils, sans avantage. Gardez simplement un seul JAXBContext (c'est la partie coûteuse) et créez un nouveau Unmarshaller chaque fois que nécessaire.
ymajoros
En fait, vous n'avez pas besoin de JAXBContexts séparés car vous pouvez transmettre plusieurs classes. Donc, si vous pouvez prédire quelles classes vont être rassemblées, vous pouvez en créer une seule partagée. En outre, la spécification JAXB exige qu'ils soient déjà threadsafe.
MauganRa