Eclipse / OSGI, Java 11, JAXB e o Classloader

Nov 23 2020

Eu tenho dois módulos Java, A e B. A fornece um modelo principal com anotações JAXB e classes auxiliares para criar coisas JAXB (criar o contexto, empacotar, descompactar etc.) B fornece classes adicionais que estão incluídas no modelo via @ XmlAnyElement (lax = true) e, portanto, deve ser incluído no contexto JAXB.

Isso funciona bem em Java simples - o carregador de classe de B vê todas as classes relevantes e pode instanciar o contexto JAXB com:

JAXBContext.newInstance(RootFromA.class, RootFromB.class)

Agora estou tentando o mesmo com OSGI (B é um plug-in do Eclipse e A é a biblioteca principal que também será usada por um módulo C de linha de comando Java simples). Depois de muitas tentativas e erros, consegui obter A e B para ver a API JAXB e a implementação por meio de importações de pacote OSGI. O problema é que chamar newInstance como acima parece usar o carregador de classe da API JAXB, não o de RootFromA e certamente não o de RootFromB. Como tal, ele nem consegue ver a implementação JAXB e reclama que não consegue encontrar a classe ContextFactory.

Consegui resolver isso chamando uma versão diferente de newInstance:

JAXBContext.newInstance(
  RootFromA.class.getPackageName()
    + ":" + RootFromB.class.getPackageName(),
  RootFromB.class.getClassLoader())

Não gosto disso, por dois motivos:

  1. Meu código de "cliente" (B sendo o cliente do auxiliar JAXB em A) deve fornecer manualmente um carregador de classe adequado.
  2. Tenho que fornecer arquivos jaxb.index em todos os pacotes referenciados que listam as classes de contexto, embora meu código esteja perfeitamente ciente deles e realmente obtenha o nome do pacote das classes.

Provavelmente não há nenhuma maneira de contornar (1), porque apenas B conhece o conjunto completo de classes e pode decidir qual carregador de classe é capaz de ver todas elas. No entanto, estou preocupado com a possibilidade de ter mais problemas depois de adicionar módulos de extensão C e D que se conectam a B por meio de pontos de extensão do Eclipse e fornecer classes adicionais para o contexto JAXB - o carregador de classe de B será capaz de vê-los?

Mas eu realmente encontraria uma maneira de me livrar dos arquivos de índice estático necessários para (2). O conjunto completo de classes de contexto é dinâmico e decidido em Java puro por um ServiceLoader e no Eclipse por um ponto de extensão. Em ambos os casos, tenho acesso direto ao conjunto completo de classes que devem pertencer ao contexto e, portanto, considero a necessidade de adicionar manualmente os arquivos jaxb.index a cada pacote redundante e, portanto, uma fonte potencial e desnecessária de erro.

Estou esquecendo de algo? Existe uma maneira "mais agradável" de fazer isso? E por que não existe um método newInstance que usa um conjunto de classes de contexto e um carregador de classe?

Respostas

MarianSchedenig Nov 26 2020 at 01:55

Parece que encontrei uma solução. Meu problema era ter que lidar com carregadores de classe para dois propósitos diferentes:

  1. O carregador de classe usado pelo JAXB para instanciar o contexto
  2. Os carregadores de classe de minhas várias classes de modelo

Conforme descrito acima, (1) pode ser especificado como um parâmetro em JAXBContext.newInstance (), mas apenas ao especificar o modelo como nomes de pacote em vez de classes de modelo individuais. Isso significa que o JAXB precisa procurar as classes por conta própria e o único carregador de classe que pode usar para fazer isso é (1) - que não pode ver todas as classes de modelo se elas estiverem espalhadas por vários pacotes.

Outro encadeamento ( Por que o JAXB não consegue encontrar meu jaxb.index ao executar dentro do Apache Felix? ) Me ensinou que o JAXB usa como padrão o carregador de classe de contexto do encadeamento para localizar a implementação do contexto. Definir isso para um carregador de classe que vê a implementação (por exemplo, o de meu próprio pacote) é suficiente para fazer JAXB encontrar sua própria implementação, o que me deixa livre para chamar minha versão preferida de newInstance () com uma matriz de classes. E como essas classes já foram carregadas, o JAXB pode usá-las como estão e não precisa se preocupar com seus carregadores de classe diferentes.

Em resumo:

Class[] myClasses = getModelClasses(); // gather all model classes, which may have different classloaders
Thread thread = Thread.currentThread();
ClassLoader originalClassLoader = thread.getContextClassLoader();
thread.setContextClassLoader(getClass().getClassLoader()); // my own bundle's classloader
JAXBContext context = JAXBContext.newInstance(myClasses);
thread.setContextClassLoader(originalClassLoader); // reset context classloader

O material de manipulação do carregador de classe de contexto pode ser agrupado em um AutoCloseable, permitindo-me simplesmente agrupar a instanciação JAXBContext em um bloco try-with-resources.