JAXB criando contexto e custo de marshallers

120

A questão é um pouco teórica, qual é o custo de criar o contexto JAXB, empacotador e não empacotador?

Descobri que meu código poderia se beneficiar mantendo o mesmo contexto JAXB e possivelmente o mesmo empacotador para todas as operações de empacotamento, em vez de criar o contexto e empacotador em cada empacotamento.

Então, qual é o custo da criação do contexto JAXB e do empacotador / desempacotador? É bom criar context + marshaller para cada operação de empacotamento ou é melhor evitá-lo?

Vladimir
fonte

Respostas:

244

Nota: Sou o líder EclipseLink JAXB (MOXy) e sou membro do grupo de especialistas JAXB 2 ( JSR-222 ).

JAXBContexté seguro para threads e deve ser criado apenas uma vez e reutilizado para evitar o custo de inicializar os metadados várias vezes. Marshallere Unmarshallernão são seguros para threads, mas são leves para criar e podem ser criados por operação.

bdoughan
fonte
7
Ótima resposta. Agora posso ter confiança com base na sua experiência como líder no JAXB.
Vladimir
7
Confio em você, mas isso pode ser encontrado em algum lugar da documentação?
Hurda
3
Está documentado para o RI: jaxb.java.net/guide/Performance_and_thread_safety.html (mas não o Moxy AFAIK)
Caoilte
39
Por favor, especifique isso no Javadoc. Não é satisfatório ter esses aspectos cruciais sem documentos.
Thomas W
6
Não é mencionado no Javadoc que JAXBContext é seguro para threads. E quase todos os exemplos de como usar o JAXB, falham em mencionar que ele deve ser criado uma vez e compartilhado entre threads. Como resultado, agora estou removendo mais um vazamento de recursos uivantes de um sistema ativo. Grrrrrrr.
Reg Whitton
42

Idealmente, você deve ter um singleton JAXBContexte instâncias locais de Marshallere Unmarshaller.

JAXBContextinstâncias são thread-safe enquanto Marshallere Unmarshallerinstâncias não são thread-safe e nunca devem ser compartilhadas entre threads.

Sahil Muthoo
fonte
obrigado pela resposta. Infelizmente eu tenho que selecionar apenas uma resposta :-)
Vladimir
15

É uma pena que isso não esteja especificamente descrito no javadoc. O que posso dizer é que o Spring usa um JAXBContext global, compartilhado entre threads, enquanto cria um novo empacotador para cada operação de empacotamento, com um comentário javadoc no código dizendo que os empacotadores JAXB não são necessariamente seguros para encadeamento.

O mesmo é dito nesta página: https://javaee.github.io/jaxb-v2/doc/user-guide/ch03.html#other-misc Miscellaneous - topics - performance - and - thread - safety .

Eu acho que criar um JAXBContext é uma operação cara, porque envolve varrer classes e pacotes para anotações. Mas medi-lo é a melhor maneira de saber.

JB Nizet
fonte
Oi @JB, ótima resposta, especialmente seus comentários sobre a medição e por que o JAXBContext é caro.
Vladimir
1
Javadoc sempre foi fraco nos fatos cruciais do ciclo de vida . Isso certamente nos dá uma repetição trivial de getters e setters de propriedades, mas quanto a saber como / onde obter ou criar uma instância, mutação e segurança de threads ... parece perder completamente esses fatores mais importantes. Suspirar :)
Thomas W
4

O JAXB 2.2 ( JSR-222 ) tem isso a dizer, na seção "4.2 JAXBContext":

Para evitar a sobrecarga envolvida na criação de uma JAXBContextinstância, um aplicativo JAXB é incentivado a reutilizar uma instância JAXBContext . Uma implementação da classe abstrata JAXBContext deve ser segura para threads , portanto, vários threads em um aplicativo podem compartilhar a mesma instância JAXBContext.

[..]

A classe JAXBContext foi projetada para ser imutável e, portanto, segura para threads. Dada a quantidade de processamento dinâmico que potencialmente poderia ocorrer ao criar uma nova instância do JAXBContxt, é recomendável que uma instância JAXBContext seja compartilhada entre encadeamentos e reutilizada o máximo possível para melhorar o desempenho do aplicativo.

Infelizmente, a especificação não faz nenhuma reivindicação em relação à segurança de thread de Unmarshallere Marshaller. Portanto, é melhor supor que não.

Martin Andersson
fonte
3

Resolvi esse problema usando:

  • JAXBContext seguro de encadeamento compartilhado e un / marschallers locais de encadeamento
  • (tão teoricamente, haverá tantas instâncias de un / marshaller quanto segmentos que os acessaram)
  • com sincronização apenas na inicialização 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
fonte
8
O ThreadLocal apresentará outros problemas sutis, sem benefícios. Apenas mantenha um único JAXBContext (essa é a parte cara) e crie um novo Unmarshaller sempre que necessário.
ymajoros
2

Melhor ainda!! Com base na boa solução da postagem acima, crie o contexto apenas uma vez no construtor e salve-o em vez da classe.

Substitua a linha:

  private Class clazz;

com este:

  private JAXBContext jc;

E o construtor principal com este:

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

portanto, no getMarshaller / getUnmarshaller, você pode remover esta linha:

  JAXBContext jc = JAXBContext.newInstance(clazz);

Essa melhoria faz, no meu caso, que o tempo de processamento caia de 60 ~ 70ms para apenas 5 ~ 10ms

tbarderas
fonte
Qual o tamanho do arquivo xml que você estava analisando. Você vê melhorias significativas em arquivos xml muito grandes?
John
1
não é realmente uma questão de grandes arquivos xml (as minas vão de apenas 2-3kb até + 6mb), mas sim um número muito grande de arquivos xml (estamos falando aqui de cerca de 10.000 solicitações xml por minuto); nesse caso criar o contexto apenas uma vez ganhando aqueles pequenos ms faz uma diferença enorme
tbarderas
1

Normalmente, resolvo problemas como esse com um ThreadLocalpadrão de classe. Dado o fato de que você precisa de um empacotador diferente para cada Classe, você pode combiná-lo com um singletonpadrão -map.

Para economizar 15 minutos de trabalho. A seguir, segue minha implementação de uma fábrica segura para threads para Jaxb Marshallers e Unmarshallers.

Permite acessar as instâncias da seguinte forma ...

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

E o código que você precisará é uma pequena classe Jaxb com a seguinte aparência:

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
fonte
10
O ThreadLocal apresentará outros problemas sutis, sem benefícios. Apenas mantenha um único JAXBContext (essa é a parte cara) e crie um novo Unmarshaller sempre que necessário.
ymajoros
Na verdade, você não precisa de JAXBContexts separados, pois pode passar em várias classes. Portanto, se você puder prever quais classes serão organizadas, poderá criar uma única compartilhada. Além disso, a especificação JAXB exige que eles já sejam seguros para threads.
MauganRa 13/09/19