Eclipse / OSGI, Java 11, JAXB y Classloader

Nov 23 2020

Tengo dos módulos de Java, A y B. A proporciona un modelo central con anotaciones JAXB y clases auxiliares para crear cosas JAXB (crear el contexto, ordenar, deshacer, etc.) B proporciona clases adicionales que se incluyen en el modelo a través de @ XmlAnyElement (lax = true) y, por lo tanto, debe agregarse al contexto JAXB.

Esto funciona bien en Java simple: el cargador de clases de B ve todas las clases relevantes y puede crear una instancia del contexto JAXB con:

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

Ahora estoy intentando lo mismo con OSGI (B es un complemento de Eclipse y A es la biblioteca central que también será utilizada por un módulo C de línea de comandos de Java simple). Después de muchas pruebas y errores, logré que A y B vieran tanto la API JAXB como la implementación a través de las importaciones de paquetes OSGI. El problema es que llamar a newInstance como arriba parece usar el cargador de clases de la API JAXB, no el de RootFromA, y ciertamente no el de RootFromB. Como tal, ni siquiera ve la implementación de JAXB y se queja de que no puede encontrar la clase ContextFactory.

Logré resolver esto llamando a una versión diferente de newInstance:

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

No me gusta esto, por dos razones:

  1. Mi código de "cliente" (siendo B el cliente del material auxiliar JAXB en A) tiene que proporcionar manualmente un cargador de clases adecuado.
  2. Tengo que proporcionar archivos jaxb.index en todos los paquetes referenciados que enumeran las clases de contexto, aunque mi código las conoce perfectamente y, de hecho, obtiene el nombre del paquete de las clases.

Probablemente no hay forma de evitarlo (1), porque solo B conoce el conjunto completo de clases y puede decidir qué cargador de clases puede verlas todas. Sin embargo, me preocupa que pueda tener más problemas una vez que agregue los módulos de extensión C y D que se conectan a B a través de los puntos de extensión de Eclipse y proporciono clases adicionales para el contexto JAXB: ¿el cargador de clases de B podrá verlos?

Pero realmente encontraría una manera de deshacerme de los archivos de índice estáticos necesarios para (2). El conjunto completo de clases de contexto es dinámico y se decide en Java simple por un ServiceLoader y en Eclipse por un punto de extensión. En ambos casos, tengo acceso directo al conjunto completo de clases que deberían pertenecer al contexto y, por lo tanto, considero tener que agregar manualmente archivos jaxb.index a cada paquete redundante y, por lo tanto, una fuente de error potencial e innecesaria.

¿Me estoy perdiendo de algo? ¿Existe una forma "mejor" de hacer esto? ¿Y por qué no hay un método newInstance que tome un conjunto de clases de contexto y un cargador de clases?

Respuestas

MarianSchedenig Nov 26 2020 at 01:55

Parece que encontré una solución. Mi problema fue tener que lidiar con los cargadores de clases para dos propósitos diferentes:

  1. El cargador de clases utilizado por JAXB para instanciar el contexto
  2. Los cargadores de clases de mis diversas clases modelo

Como se describió anteriormente, (1) se puede especificar como un parámetro en JAXBContext.newInstance (), pero solo cuando se especifica el modelo como nombres de paquetes en lugar de clases de modelos individuales. Esto significa que JAXB tiene que buscar las clases por sí solo, y el único cargador de clases que puede usar para hacerlo es (1), que no puede ver todas las clases del modelo si están distribuidas en varios paquetes.

Otro hilo ( ¿Por qué JAXB no puede encontrar mi jaxb.index cuando se ejecuta dentro de Apache Felix? ) Me enseñó que JAXB utiliza por defecto el cargador de clases de contexto del hilo para localizar la implementación del contexto. Establecer eso en un cargador de clases que ve la implementación (por ejemplo, la de mi propio paquete) es suficiente para que JAXB encuentre su propia implementación, lo que me deja libre para llamar a mi versión preferida de newInstance () con una matriz de clases. Y dado que estas clases ya se han cargado, JAXB puede usarlas tal cual y no tiene que preocuparse por sus diferentes cargadores de clases.

En breve:

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

El material de manipulación del cargador de clases de contexto se puede envolver en un AutoCloseable, lo que me permite simplemente envolver la instanciación de JAXBContext en un bloque try-with-resources.