JAXB creazione di contesto e costo marshaller


120

La domanda è un po 'teorica, qual è il costo della creazione di un contesto JAXB, marshaller e unmarshaller?

Ho scoperto che il mio codice potrebbe trarre vantaggio dal mantenere lo stesso contesto JAXB e possibilmente lo stesso marshaller per tutte le operazioni di marshalling piuttosto che creare contesto e marshaller su ogni marshalling.

Quindi qual è il costo per creare un contesto JAXB e un marshaller / unmarshaller? Va bene creare contesto + marshaller per ogni operazione di marshalling o è meglio evitarlo?

Risposte:


244

Nota: sono il lead di EclipseLink JAXB (MOXy) e un membro del gruppo di esperti JAXB 2 ( JSR-222 ).

JAXBContextè thread-safe e deve essere creato una sola volta e riutilizzato per evitare il costo di inizializzazione più volte dei metadati. Marshallere Unmarshallernon sono thread-safe, ma sono leggeri da creare e possono essere creati per operazione.


7
Bella risposta. Ora posso essere fiducioso sulla base della tua esperienza come responsabile di JAXB.
Vladimir

7
Mi fido di te, ma si trova da qualche parte nella documentazione?
Hurda

3
È documentato per il RI: jaxb.java.net/guide/Performance_and_thread_safety.html (ma non Moxy AFAIK)
Caoilte

39
Si prega di specificarlo nel Javadoc. Non è soddisfacente che questi aspetti cruciali non siano documentati.
Thomas W,

6
Non è menzionato nel Javadoc che JAXBContext è thread-safe. E quasi ogni esempio di come utilizzare JAXB, non menziona che dovrebbe essere creato una volta e condiviso tra i thread. Di conseguenza ora sto rimuovendo un'altra perdita di risorse ululante da un sistema live. Grrrrrrr.
Reg Whitton

42

Idealmente, dovresti avere JAXBContextistanze singleton e locali di Marshallere Unmarshaller.

JAXBContextle istanze sono thread-safe mentre Marshallere le Unmarshalleristanze non sono thread-safe e non dovrebbero mai essere condivise tra thread.


Grazie per la risposta. Purtroppo devo selezionare una sola risposta :-)
Vladimir

15

È un peccato che questo non sia descritto specificamente nel javadoc. Quello che posso dire è che Spring utilizza un JAXBContext globale, condiviso tra i thread, mentre crea un nuovo marshaller per ogni operazione di marshalling, con un commento javadoc nel codice che dice che i marshaller JAXB non sono necessariamente thread-safe.

Lo stesso si dice in questa pagina: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-miscellaneous-topics-performance-and-thread-safety .

Immagino che la creazione di un JAXBContext sia un'operazione costosa, perché implica la scansione di classi e pacchetti per le annotazioni. Ma misurarlo è il modo migliore per saperlo.


Ciao @JB, ottima risposta, in particolare i tuoi commenti sulla misurazione e sul perché JAXBContext è costoso.
Vladimir

1
Javadoc è sempre stato debole sui fatti cruciali del ciclo di vita . Sicuramente ci dà una banale ripetizione di proprietà getter e setter, ma per quanto riguarda il sapere come / dove ottenere o creare un'istanza, la mutazione e la sicurezza del thread ... sembra mancare completamente quei fattori più importanti. Sigh :)
Thomas W,

4

JAXB 2.2 ( JSR-222 ) ha questo da dire, nella sezione "4.2 JAXBContext":

Per evitare l'overhead coinvolto nella creazione di JAXBContextun'istanza, un'applicazione JAXB è incoraggiata a riutilizzare un'istanza JAXBContext . È richiesta un'implementazione della classe astratta JAXBContext per essere thread-safe , quindi più thread in un'applicazione possono condividere la stessa istanza JAXBContext.

[..]

La classe JAXBContext è progettata per essere immutabile e quindi sicura per i thread. Data la quantità di elaborazione dinamica che potrebbe avere luogo durante la creazione di una nuova istanza di JAXBContxt, si consiglia di condividere un'istanza di JAXBContext tra i thread e riutilizzarla il più possibile per migliorare le prestazioni dell'applicazione.

Sfortunatamente, la specifica non fa alcun reclamo riguardo alla sicurezza dei thread di Unmarshallere Marshaller. Quindi è meglio presumere che non lo siano.


3

Ho risolto questo problema utilizzando:

  • JAXBContext thread safe condiviso e un / marschallers locale thread
  • (quindi in teoria, ci saranno tante istanze un / marshaller quanti sono i thread che vi hanno avuto accesso)
  • con sincronizzazione solo sull'inizializzazione di 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");
        }
    }
}

8
ThreadLocal introdurrà altri problemi sottili, senza alcun vantaggio. Mantieni un solo JAXBContext (questa è la parte costosa) e crea un nuovo Unmarshaller ogni volta che è necessario.
ymajoros

2

Anche meglio!! Sulla base della buona soluzione del post sopra, crea il contesto una sola volta nel costruttore e salvalo invece della classe.

Sostituisci la riga:

  private Class clazz;

con questo:

  private JAXBContext jc;

E il costruttore principale con questo:

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

quindi in getMarshaller / getUnmarshaller puoi rimuovere questa riga:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Questo miglioramento fa sì che, nel mio caso, i tempi di elaborazione scendano da 60 ~ 70 ms a soli 5 ~ 10 ms


Quanto era grande il file xml che stavi analizzando. Noti miglioramenti significativi con file xml di grandi dimensioni?
Giovanni

1
non si tratta proprio di file xml di grandi dimensioni (le miniere vanno da appena 2-3kb fino a + 6mb), ma piuttosto di un numero molto elevato di file xml (stiamo parlando di circa 10.000 richieste xml al minuto); in tal caso creare il contesto solo una volta guadagnando quei piccoli ms fa un'enorme differenza
tbarderas

1

Di solito risolvo problemi come questo con un ThreadLocalmodello di classe. Dato che hai bisogno di un marshaller diverso per ogni Classe, puoi combinarlo con un singletonmodello -map.

Per farti risparmiare 15 minuti di lavoro. Di seguito la mia implementazione di una Factory thread-safe per Jaxb Marshallers e Unmarshaller.

Ti permette di accedere alle istanze come segue ...

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

E il codice di cui avrai bisogno è una piccola classe Jaxb che ha il seguente aspetto:

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

10
ThreadLocal introdurrà altri problemi sottili, senza alcun vantaggio. Mantieni un solo JAXBContext (questa è la parte costosa) e crea un nuovo Unmarshaller ogni volta che è necessario.
ymajoros

In realtà non hai bisogno di JAXBContext separati poiché puoi passare più classi. Quindi, se puoi prevedere quali classi verranno organizzate, puoi crearne una singola condivisa. Inoltre, le specifiche JAXB richiedono che siano già threadsafe.
MauganRa
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.