Чтобы установить контекст этого поста, давайте рассмотрим исходный вариант использования, приведший к проблеме, которая привела к решению.
Вариант использования
Я хотел использовать библиотеку Рима для потребления / производства каналов RSS / Atom. Я использовал его раньше в традиционных проектах JEE, но никогда в контейнере OSGi.
Окружение
Я использую Fuse ESB 4.1, коммерческий дистрибутив Progress Apache ServiceMix 4 OSGi, основанный на Apache Felix . Используемая реализация JAX-RS — Apache CXF v2.2 . Все это работает на моем MacBook Pro, под Java 1.6.
Я развернул jar Rome 1.0 в Fuse ESB 4. Этот jar содержит необходимые метаданные в своем манифесте для развертывания в контейнере OSGi.
Код
Для упрощения предположим, что класс ресурсов JAX-RS публикует канал:
SyndFeed feed = new SyndFeedImpl();
feed.setFeedType("rss_2.0");
feed.setTitle("Sample Feed (created with ROME)");
feed.setLink("http://rome.dev.java.net");
feed.setDescription("This feed has been created using ROME (Java syndication utilities");
// ... add entries
StringWriter sw = new StringWriter();
SyndFeedOutput output = new SyndFeedOutput();
output.output(feed, sw);
return Response.ok(sw.toString()).build();
Этот код развертывается как комплект OSGi, в зависимости от спецификаций CXF, JAX-RS и Rome (среди прочего).
Эта проблема
Когда я запускаю этот код и вызываю URL для этого ресурса, я получаю странное исключение, ссылающееся на класс SyndFeedImpl, который не инициализируется правильно. Какая? Я только назвал конструктор по умолчанию, что не так с этим ??
org.apache.cxf.interceptor.Fault: Could not initialize class com.sun.syndication.feed.synd.SyndFeedImpl
at org.apache.cxf.service.invoker.AbstractInvoker.createFault(AbstractInvoker.java:148)
at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:114)
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:122)
at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:70)
... [snip]
Копаясь в исходном коде Рима , я обнаружил, что все плагины (компоненты ввода-вывода + различные вспомогательные реализации) указаны в файле свойств где-то на пути к классам (некоторый DI для бедного человека). Рим читает этот файл, чтобы он мог создавать экземпляры этих классов. Но для этого нужно сначала попросить загрузчик классов загрузить их. Давайте посмотрим, как это реализовано:
PropertiesLoader loader = (PropertiesLoader)
clMap.get(Thread.currentThread().getContextClassLoader());
if (loader == null) {
try {
loader = new PropertiesLoader(MASTER_PLUGIN_FILE, EXTRA_PLUGIN_FILE);
clMap.put(Thread.currentThread().getContextClassLoader(), loader);
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
return loader;
Итак, здесь мы ясно видим, что при чтении конфигурации плагинов эти свойства хранятся вместе с загрузчиком классов, который будет использоваться для загрузки классов. Используемый загрузчик классов — Thread.currentThread (). GetContextClassLoader ().
BAM!
Во время выполнения при проверке того, к чему разрешает этот загрузчик классов, мы обнаруживаем, что для него установлено:
System.out.println(Thread.currentThread().getContextClassLoader().toString());
$ [Apache ServiceMix CXF Transport for OSGi (org.apache.servicemix.cxf.transport.osgi)]
Который является загрузчиком классов для совершенно другого пакета. Однако для достижения истинной модульности архитектура OSGi сообщает нам (п. 3.4, второй параграф), что для каждого пакета существует один загрузчик классов. Таким образом, Рим не может загрузить и создать свои плагины (в данном случае динамически).
Решение 1
Промедление, и тем самым, счет вашего клиента еще больше.
Решение 2
Откройте римский проект Maven в Netbeans и исправьте код. Но разве загрузка классов не является сложным делом? это зависит. Один из простых способов это исправить — выяснить, как работает OSGi. Когда он устанавливает пакет, даже до разрешения зависимостей этого пакета, фреймворк создает загрузчик классов, приватное и удобное место, где другие классы пакета не будут мешать / скрываться другими (читай: jar hell). Этот загрузчик классов является единственным, кто может загружать классы из этого комплекта, поскольку только он видит их (не совсем верно, но в любом случае).
Таким образом, легко запросить у класса загрузчик классов, просто сделав что-то вроде:
ClassLoader cl = this.getClass().getClassLoader();
Итак, теперь, если я изменяю код для использования этого загрузчика классов и проверяю, к чему он приводит, я получаю:
System.out.println(this.getClass().getClassLoader().toString());
$ [211.0]
Который представляет римский идентификатор пакета внутри Fuse ESB 4.
Вывод
Я выбрал решение 2;) Какое решение заставило меня перекомпилировать специальную версию Rome и сохранить ее в нашем внутреннем репозитории Maven.
Я узнал, что иногда осмысление банки — это нечто большее, чем просто хороший манифест. Разработчики корпоративных систем должны знать о таких вещах, прежде чем просто добавить банку в проект.
Может быть, другие решения были бы жизнеспособными: заголовки манифеста видимости друзей Equinox ? Динамический-импорт? (Хотя я где- то читал, что это должно быть использовано как последнее средство, поскольку оно нарушает принципы модульности OSGi).
Я был бы рад услышать другие точки зрения.
JS.