Eclipse / OSGI, Java 11, JAXB e Classloader
Ho due moduli Java, A e B. A fornisce un modello di base con annotazioni JAXB e classi di supporto per creare cose JAXB (creazione del contesto, marshalling, unmarshalling, ecc.) B fornisce classi aggiuntive che sono incluse nel modello tramite @ XmlAnyElement (lax = true) e deve quindi essere aggiunto al contesto JAXB.
Funziona bene in Java semplice: il classloader di B vede tutte le classi rilevanti e può istanziare il contesto JAXB con:
JAXBContext.newInstance(RootFromA.class, RootFromB.class)
Ora sto provando lo stesso con OSGI (B è un plug-in di Eclipse e A è la libreria principale che verrà utilizzata anche da un semplice modulo C della riga di comando Java). Dopo molte prove ed errori, sono riuscito a ottenere A e B per vedere sia l'API JAXB che l'implementazione tramite le importazioni di pacchetti OSGI. Il problema è che chiamare newInstance come sopra sembra utilizzare il classloader dell'API JAXB, non quello di RootFromA, e certamente non quello di RootFromB. In quanto tale, non riesce nemmeno a vedere l'implementazione JAXB e si lamenta di non riuscire a trovare la classe ContextFactory.
Sono riuscito a risolverlo chiamando una versione diversa di newInstance:
JAXBContext.newInstance(
RootFromA.class.getPackageName()
+ ":" + RootFromB.class.getPackageName(),
RootFromB.class.getClassLoader())
Non mi piace per due motivi:
- Il mio codice "client" (B è il client dell'helper JAXB in A) deve fornire manualmente un classloader adatto.
- Devo fornire file jaxb.index in tutti i pacchetti referenziati che elencano le classi di contesto, anche se il mio codice ne è perfettamente consapevole e in realtà ottiene il nome del pacchetto dalle classi.
Probabilmente non c'è modo di aggirare (1), perché solo B conosce l'insieme completo di classi e può decidere quale classloader è in grado di vederle tutte. Sono preoccupato però che potrei incontrare più problemi una volta che aggiungo i moduli di estensione C e D che si collegano a B tramite i punti di estensione Eclipse e fornisco classi aggiuntive per il contesto JAXB: il classloader di B sarà in grado di vederli?
Ma troverei davvero un modo per sbarazzarmi dei file di indice statico richiesti per (2). Il set completo di classi di contesto è dinamico e deciso in Java semplice da un ServiceLoader e in Eclipse da un punto di estensione. In entrambi i casi ho accesso diretto al set completo di classi che dovrebbero appartenere al contesto, quindi considero ridondante l'aggiunta manuale di file jaxb.index a ciascun pacchetto e quindi una potenziale e non necessaria fonte di errore.
Mi sto perdendo qualcosa? C'è un modo "più carino" per farlo? E perché non esiste un metodo newInstance che accetta un set di classi di contesto e un classloader?
Risposte
Sembra che abbia trovato una soluzione. Il mio problema era dover gestire i classloader per due scopi diversi:
- Il classloader utilizzato da JAXB per istanziare il contesto
- I classloader delle mie varie classi di modelli
Come descritto sopra, (1) può essere specificato come parametro in JAXBContext.newInstance (), ma solo quando si specifica il modello come nomi di pacchetto invece di singole classi di modello. Ciò significa che JAXB deve cercare le classi da solo, e l'unico classloader che può utilizzare per farlo è (1), che non può vedere tutte le classi del modello se sono distribuite su più bundle.
Un altro thread ( perché JAXB non riesce a trovare il mio jaxb.index durante l'esecuzione in Apache Felix? ) Mi ha insegnato che JAXB utilizza per impostazione predefinita il classloader del contesto del thread per individuare l'implementazione del contesto. Impostarlo su un classloader che vede l'implementazione (ad esempio quella del mio bundle) è sufficiente per fare in modo che JAXB trovi la propria implementazione, il che mi lascia libero di chiamare la mia versione preferita di newInstance () con un array di classi. E poiché queste classi sono già state caricate, JAXB può usarle così come sono e non deve preoccuparsi dei loro diversi classloader.
In 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
Il materiale di manipolazione del classloader del contesto può essere avvolto in un AutoCloseable, permettendomi di racchiudere semplicemente l'istanza di JAXBContext in un blocco try-with-resources.