La question est un peu théorique, quel est le coût de la création d'un contexte JAXB, marshaller et unmarshaller?
J'ai constaté que mon code pourrait tirer avantage de conserver le même contexte JAXB et éventuellement 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? Peut-on créer un contexte + marshaller pour chaque opération de marshaling ou mieux l'éviter?
Remarque: Je suis le EclipseLink JAXB (MOXy) responsable et membre du groupe d'experts JAXB 2 ( JSR-222 ).
JAXBContext
est thread-safe et ne devrait être créé qu'une seule fois et réutilisé afin d'éviter le coût d'initialisation des métadonnées plusieurs fois. Marshaller
et Unmarshaller
ne sont pas thread-safe, mais sont légers à créer et peuvent être créés par opération.
Idéalement, vous devriez avoir un singleton JAXBContext
et des instances locales de Marshaller
et Unmarshaller
.
JAXBContext
les instances sont thread-safe alors que Marshaller
et Unmarshaller
sont en instance not thread-safe et ne doivent jamais être partagés entre les threads.
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- sûr.
La même chose est dite sur cette page: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-misc 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 de classes et de packages pour les annotations. Mais le mesurer est le meilleur moyen de savoir.
J'ai résolu ce problème en utilisant le thread partagé sûr JAXBContext et le thread local n/marschallers (donc théoriquement, il y en aura autant n/marshaller comme il y a des threads qui y ont accédé) avec synchronisation uniquement à l’initialisation de n/marshaller.
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");
}
}
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 instance
JAXBContext
, une application JAXB est encouragée à réutiliser une instance JAXBContext . Une implémentation de la classe abstraite JAXBContext doit être adaptée aux threads ; par conséquent, plusieurs threads d'une application peuvent partager la même instance JAXBContext.[..]
La classe JAXBContext est conçue pour être immuable et donc threadsafe. Compte tenu de la quantité de traitement dynamique susceptible de se produire lors de la création d'une nouvelle instance de JAXBContxt, il est recommandé de partager une instance de JAXBContext sur plusieurs threads et de la réutiliser autant que possible afin d'améliorer les performances de l'application.
Malheureusement, la spécification ne fait aucune déclaration concernant la sécurité du thread de Unmarshaller
et Marshaller
. Il est donc préférable de supposer qu'ils ne le sont pas.
Encore mieux!! Sur la base de la bonne solution de l'article précédent, créez le contexte juste une 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);
}
ainsi, 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 à 5 ~ 10ms
Je résous généralement des problèmes comme celui-ci avec un modèle de classe ThreadLocal
. Compte tenu du fait que vous avez besoin d'un marshaller différent pour chaque classe, vous pouvez le combiner avec un motif de map singleton
-.
Pour vous faire gagner 15 minutes de travail. Voici ma mise en œuvre d’une usine thread-safe pour les Jaxb Marshallers et les 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 se présente comme suit:
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;
}
}