Eclipse / OSGI, Java 11, JAXB und der Classloader
Ich habe zwei Java-Module, A und B. A bietet ein Kernmodell mit JAXB-Annotationen und Hilfsklassen zum Erstellen von JAXB-Aufgaben (Erstellen des Kontexts, Marshalling, Unmarshalling usw.). B stellt zusätzliche Klassen bereit, die über @ im Modell enthalten sind XmlAnyElement (lax = true) und muss daher zum JAXB-Kontext hinzugefügt werden.
Dies funktioniert gut in einfachem Java - Der Classloader von B sieht alle relevanten Klassen und kann den JAXB-Kontext instanziieren mit:
JAXBContext.newInstance(RootFromA.class, RootFromB.class)
Jetzt versuche ich dasselbe mit OSGI (B ist ein Eclipse-Plug-In und A ist die Kernbibliothek, die auch von einem einfachen Java-Befehlszeilenmodul C verwendet wird). Nach langem Ausprobieren habe ich es geschafft, dass A und B sowohl die JAXB-API als auch die Implementierung über OSGI-Paketimporte sehen. Das Problem ist, dass beim Aufrufen von newInstance wie oben der Klassenlader der JAXB-API verwendet wird, nicht der von RootFromA und schon gar nicht der von RootFromB. Daher wird die JAXB-Implementierung nicht einmal angezeigt und es wird beanstandet, dass die ContextFactory-Klasse nicht gefunden werden kann.
Ich habe es geschafft, dies durch Aufrufen einer anderen Version von newInstance zu beheben:
JAXBContext.newInstance(
RootFromA.class.getPackageName()
+ ":" + RootFromB.class.getPackageName(),
RootFromB.class.getClassLoader())
Ich mag das aus zwei Gründen nicht:
- Mein "Client" -Code (B ist der Client des JAXB-Hilfsprogramms in A) muss manuell einen passenden Klassenladeprogramm bereitstellen.
- Ich muss jaxb.index-Dateien in allen referenzierten Paketen bereitstellen, in denen die Kontextklassen aufgelistet sind, obwohl mein Code sie genau kennt und tatsächlich den Paketnamen von den Klassen erhält.
Daran führt wahrscheinlich kein Weg vorbei (1), da nur B den gesamten Satz von Klassen kennt und entscheiden kann, wessen Klassenlader alle sehen kann. Ich mache mir jedoch Sorgen, dass ich weitere Probleme bekommen könnte, wenn ich die Erweiterungsmodule C und D hinzufüge, die sich über Eclipse-Erweiterungspunkte in B einbinden und zusätzliche Klassen für den JAXB-Kontext bereitstellen. Kann der Klassenlader von B diese sehen?
Aber ich würde wirklich einen Weg finden, die für (2) erforderlichen statischen Indexdateien loszuwerden. Der vollständige Satz von Kontextklassen ist dynamisch und wird in einfachem Java von einem ServiceLoader und in Eclipse von einem Erweiterungspunkt festgelegt. In beiden Fällen habe ich direkten Zugriff auf alle Klassen, die zum Kontext gehören sollten, und erwäge daher, jedem redundanten Paket manuell jaxb.index-Dateien hinzuzufügen, was eine potenzielle und unnötige Fehlerquelle darstellt.
Vermisse ich etwas Gibt es einen "schöneren" Weg, dies zu tun? Und warum gibt es keine newInstance-Methode, die eine Reihe von Kontextklassen und einen Klassenladeprogramm verwendet?
Antworten
Es scheint, ich habe eine Lösung gefunden. Mein Problem war, dass ich mich mit Klassenladern für zwei verschiedene Zwecke befassen musste:
- Der von JAXB zum Instanziieren des Kontexts verwendete Klassenladeprogramm
- Die Klassenlader meiner verschiedenen Modellklassen
Wie oben beschrieben, kann (1) in JAXBContext.newInstance () als Parameter angegeben werden, jedoch nur, wenn das Modell als Paketnamen anstelle einzelner Modellklassen angegeben wird. Dies bedeutet, dass JAXB die Klassen selbst nachschlagen muss. Der einzige Klassenladeprogramm, mit dem dies möglich ist, ist (1). Dabei können nicht alle Modellklassen angezeigt werden, wenn sie auf mehrere Bundles verteilt sind.
Ein anderer Thread ( Warum kann JAXB meinen jaxb.index nicht finden, wenn er in Apache Felix ausgeführt wird? ) Hat mir beigebracht, dass JAXB standardmäßig den Thread-Kontextklassenlader verwendet, um die Kontextimplementierung zu finden. Das Festlegen eines Klassenladeprogramms, das die Implementierung sieht (z. B. das meines eigenen Bundles), reicht aus, damit JAXB eine eigene Implementierung findet, sodass ich meine bevorzugte Version von newInstance () mit einem Array von Klassen aufrufen kann. Und da diese Klassen bereits geladen wurden, kann JAXB sie unverändert verwenden und muss sich nicht um ihre verschiedenen Klassenladeprogramme kümmern.
Zusamenfassend:
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
Das Manipulationsmaterial für den Kontextklassenlader kann in ein AutoCloseable eingeschlossen werden, sodass ich die JAXBContext-Instanziierung einfach in einen Try-with-Resources-Block einschließen kann.