Eclipse / OSGI, Java 11, JAXB i Classloader
Mam dwa moduły Java, A i B. A zapewnia model podstawowy z adnotacjami JAXB i klasami pomocniczymi do tworzenia rzeczy JAXB (tworzenie kontekstu, marshalling, unmarshalling itp.) B zapewnia dodatkowe klasy, które są zawarte w modelu przez @ XmlAnyElement (lax = true) i dlatego musi zostać dodany do kontekstu JAXB.
Działa to dobrze w zwykłej Javie - moduł ładujący klasy B widzi wszystkie odpowiednie klasy i może utworzyć wystąpienie kontekstu JAXB za pomocą:
JAXBContext.newInstance(RootFromA.class, RootFromB.class)
Teraz próbuję tego samego z OSGI (B to wtyczka Eclipse, a A to podstawowa biblioteka, która będzie również używana przez zwykły moduł wiersza poleceń Java C). Po wielu próbach i błędach udało mi się uzyskać A i B, aby zobaczyć zarówno API JAXB, jak i implementację za pośrednictwem importu pakietów OSGI. Problem polega na tym, że wywołanie newInstance w powyższy sposób wydaje się korzystać z modułu ładującego klasy interfejsu API JAXB, a nie z RootFromA, a już na pewno nie z RootFromB. W związku z tym nie widzi nawet implementacji JAXB i narzeka, że nie może znaleźć klasy ContextFactory.
Udało mi się rozwiązać ten problem, wywołując inną wersję newInstance:
JAXBContext.newInstance(
RootFromA.class.getPackageName()
+ ":" + RootFromB.class.getPackageName(),
RootFromB.class.getClassLoader())
Nie podoba mi się to z dwóch powodów:
- Mój kod „klienta” (B jest klientem elementów pomocniczych JAXB w A) musi ręcznie udostępniać odpowiedni program ładujący klasy.
- Muszę udostępnić pliki jaxb.index we wszystkich pakietach, do których istnieją odniesienia, które zawierają listę klas kontekstu, mimo że mój kod doskonale o nich wie i faktycznie pobiera nazwę pakietu z klas.
Prawdopodobnie nie ma innego wyjścia (1), ponieważ tylko B zna pełny zestaw klas i może zdecydować, czyj program ładujący klasy jest w stanie zobaczyć je wszystkie. Martwię się jednak, że mogę napotkać więcej problemów, gdy dodam moduły rozszerzeń C i D, które podłączają się do B za pośrednictwem punktów rozszerzeń Eclipse i dostarczają dodatkowe klasy dla kontekstu JAXB - czy program ładujący klasy B będzie w stanie je zobaczyć?
Ale naprawdę znalazłbym sposób na pozbycie się statycznych plików indeksowych wymaganych dla (2). Pełny zestaw klas kontekstu jest dynamiczny i ustalany w zwykłej Javie przez ServiceLoader, aw Eclipse przez punkt rozszerzenia. W obu przypadkach mam bezpośredni dostęp do pełnego zestawu klas, które powinny należeć do kontekstu, dlatego rozważam konieczność ręcznego dodawania plików jaxb.index do każdego pakietu, które są zbędne, a więc potencjalne i niepotrzebne źródło błędów.
Czy coś mi brakuje? Czy jest na to „przyjemniejszy” sposób? Dlaczego nie ma metody newInstance, która pobiera zestaw klas kontekstu i moduł ładujący klasy?
Odpowiedzi
Wygląda na to, że znalazłem rozwiązanie. Mój problem polegał na tym, że musiałem radzić sobie z programami ładującymi klasy w dwóch różnych celach:
- Moduł ładujący klasy używany przez JAXB do tworzenia wystąpienia kontekstu
- Programy ładujące z moich różnych klas modeli
Jak opisano powyżej, (1) można określić jako parametr w JAXBContext.newInstance (), ale tylko w przypadku określania modelu jako nazw pakietów zamiast poszczególnych klas modelu. Oznacza to, że JAXB musi samodzielnie wyszukiwać klasy, a jedynym programem ładującym klasy, którego może to zrobić, jest (1) - który nie widzi wszystkich klas modelu, jeśli są one rozmieszczone w wielu pakietach.
Inny wątek ( Dlaczego JAXB nie może znaleźć mojego jaxb.index podczas pracy w Apache Felix? ) Nauczył mnie, że JAXB domyślnie używa modułu ładującego kontekst wątku do zlokalizowania implementacji kontekstu. Ustawienie tego na program ładujący klasy, który widzi implementację (np. Mojego własnego pakietu) jest wystarczające, aby JAXB znalazł własną implementację, co pozwala mi wywołać moją preferowaną wersję newInstance () z tablicą klas. A ponieważ te klasy zostały już załadowane, JAXB może ich używać w takiej postaci, w jakiej są, i nie musi przejmować się różnymi programami ładującymi klasy.
W skrócie:
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
Elementy manipulowania programem ładującym klasy kontekstu mogą być opakowane w element AutoCloseable, co pozwala mi po prostu zawinąć wystąpienie JAXBContext w blok try-with-resources.