Eclipse / OSGI, Java 11, JAXB и загрузчик классов

Nov 23 2020

У меня есть два модуля Java, A и B. A предоставляет базовую модель с аннотациями JAXB и вспомогательными классами для создания вещей JAXB (создание контекста, маршаллинг, демаршаллинг и т. Д.) B предоставляет дополнительные классы, которые включаются в модель через @ XmlAnyElement (lax = true) и поэтому должен быть добавлен в контекст JAXB.

Это отлично работает на простой Java - загрузчик классов B видит все соответствующие классы и может создать экземпляр контекста JAXB с помощью:

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

Теперь я пробую то же самое с OSGI (B - это плагин Eclipse, а A - основная библиотека, которая также будет использоваться простым модулем командной строки Java C). После долгих проб и ошибок мне удалось получить A и B, чтобы увидеть как JAXB API, так и реализацию через импорт пакетов OSGI. Проблема в том, что при вызове newInstance, как указано выше, похоже, используется загрузчик классов JAXB API, а не RootFromA и, конечно, не RootFromB. Таким образом, он не видит даже реализации JAXB и жалуется, что не может найти класс ContextFactory.

Мне удалось решить эту проблему, вызвав другую версию newInstance:

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

Мне это не нравится по двум причинам:

  1. Мой «клиентский» код (B является клиентом вспомогательного материала JAXB в A) должен вручную предоставить подходящий загрузчик классов.
  2. Я должен предоставить файлы jaxb.index во всех указанных пакетах, в которых перечислены классы контекста, хотя мой код прекрасно их знает и фактически получает имя пакета из классов.

Вероятно, нет никакого пути (1), потому что только B знает полный набор классов и может решить, чей загрузчик классов может видеть их все. Я беспокоюсь, что у меня могут возникнуть дополнительные проблемы, если я добавлю модули расширения C и D, которые подключаются к B через точки расширения Eclipse и предоставят дополнительные классы для контекста JAXB - сможет ли загрузчик классов B их увидеть?

Но я бы действительно нашел способ избавиться от файлов статического индекса, необходимых для (2). Полный набор классов контекста является динамическим и определяется в простой Java с помощью ServiceLoader и в Eclipse с помощью точки расширения. В обоих случаях у меня есть прямой доступ к полному набору классов, которые должны принадлежать контексту, и поэтому я считаю, что нужно вручную добавлять файлы jaxb.index в каждый избыточный пакет и, следовательно, потенциальный и ненужный источник ошибки.

Я что-то упускаю? Есть ли способ сделать это лучше? И почему нет метода newInstance, который принимает набор классов контекста и загрузчик классов?

Ответы

MarianSchedenig Nov 26 2020 at 01:55

Кажется, я нашел решение. Моя проблема заключалась в том, чтобы иметь дело с загрузчиками классов для двух разных целей:

  1. Загрузчик классов, используемый JAXB для создания экземпляра контекста
  2. Загрузчики классов моих различных классов моделей

Как описано выше, (1) можно указать как параметр в JAXBContext.newInstance (), но только при указании модели в виде имен пакетов, а не отдельных классов модели. Это означает, что JAXB должен искать классы самостоятельно, и единственный загрузчик классов, который он может использовать для этого, - это (1), который не может видеть все классы модели, если они распределены по нескольким пакетам.

Другой поток ( почему JAXB не может найти мой jaxb.index при запуске внутри Apache Felix? ) Научил меня, что JAXB по умолчанию использует загрузчик классов контекста потока для поиска реализации контекста. Установка этого для загрузчика классов, который видит реализацию (например, моего собственного пакета), достаточна, чтобы заставить JAXB найти свою собственную реализацию, что дает мне возможность вызывать мою предпочтительную версию newInstance () с массивом классов. А поскольку эти классы уже загружены, JAXB может использовать их как есть и не заботиться об их различных загрузчиках классов.

Коротко:

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

Материал для управления загрузчиком классов контекста может быть заключен в AutoCloseable, что позволяет мне просто заключить экземпляр JAXBContext в блок try-with-resources.