Eclipse / OSGI, Java 11, JAXB e o Classloader
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:
- Meu código de "cliente" (B sendo o cliente do auxiliar JAXB em A) deve fornecer manualmente um carregador de classe adequado.
- 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
Parece que encontrei uma solução. Meu problema era ter que lidar com carregadores de classe para dois propósitos diferentes:
- O carregador de classe usado pelo JAXB para instanciar o contexto
- 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.