Статьи

Понимание того, как решаются пакеты OSGI

Я хотел бы рассмотреть, как решаются пакеты OSGI, и использовать Apache Karaf для демонстрации. Karaf — это полнофункциональный контейнер OSGI, основанный на ядре Apache Felix и являющийся краеугольным камнем контейнера интеграции Apache ServiceMix . В первой части я расскажу, как пакеты разрешаются с помощью инфраструктуры OSGI. Во второй части я продемонстрирую каждое правило, используя Apache Karaf. Давайте начнем.

Правила разрешения пакетов

Жизненный цикл пакета OSGI определяет возможные состояния и переходы для пакета. Мы будем обсуждать состояние «Разрешено» пакета, что означает состояние, которого он может достичь после «Установки» и когда все его необходимые зависимости удовлетворены. Традиционная загрузка классов Java чувствительна к исключениям ClassCastException во время выполнения, когда два класса с одним и тем же полностью определенным именем из двух разных загрузчиков классов смешиваются и один используется в неправильном пространстве пути к классам. Одна из основных целей OSGI состоит в том, чтобы избежать такого рода исключений во время выполнения путем разрешения всех зависимостей во время развертывания, так как идея «быстрого» отказа во время развертывания будет легче отладить, чем пытаться отследить проблемы загрузки классов во время выполнения. Подумайте о том, как раздражают некоторые классы, которые не найдены, или исключения приведения классов, например, для отладки в развертывании Weblogic. OSGI решает это. Чтобы пакет достиг состояния «Разрешено», у него должны быть выполнены зависимости. Подумайте о «быстром отказе» в подходе к разрешению пакетов следующим образом: если вы используете весеннее приложение, и один из ваших компонентов не может быть подключен должным образом из-за отсутствия определения компонента, вы узнаете об этом во время развертывания, а не когда клиент вызывая ваш код. Тот же принцип применяется с OSGI; вместо зависимостей проводки на уровне объекта мы будем связывать зависимости модуля и загрузки классов.
Тривиальное объяснение того, как пакет разрешает свои зависимости, может выглядеть так: если пакет импортирует (Import-Package) определенный пакет, этот пакет должен быть доступен для экспорта другого пакета (Export-Package). Если у пакета A есть Import-Package: org.apache.foo, то должен быть развернут пакет, у которого есть Export-Package: org.apache.foo

Для каждого объявления пакета Import-Package должен быть соответствующий Export-Package с тем же пакетом

Пакеты также могут прикреплять другие атрибуты к пакетам, которые он импортирует или экспортирует. Что если мы добавим атрибут version в наш пример:

Bundle-Name: Bundle A
Import-Package: org.apache.foo;version="1.2.0"

Это означает, что пакет A зависит от пакета org.apache.foo с минимальной версией 1.2.0. Да, вы правильно прочитали. Хотя в OSGI вы можете указать диапазон версий, если вы не указываете диапазон, а используете фиксированную версию, это приведет к значению «минимума» фиксированного значения. Если для того же пакета существует более высокая версия, будет использоваться более высокая версия . Таким образом, пакет A не будет разрешен правильно, если нет соответствующего пакета B, который экспортирует требуемый пакет:

Bundle-Name: Bundle B
Export-Package: org.apache.foo;version="1.2.0"

Обратите внимание, что обратное неверно … Если Bundle B экспортировал версию 1.2.0, Bundle A не требуется указывать версию 1.2.0. Он может использовать этот импорт и решить просто отлично:

Bundle-Name: Bundle A
Import-Package: org.apache.foo

Это связано с тем, что при импорте объявляются нужные им версии. Экспортируемая версия не указывает ничего, что должен использовать импортирующий комплект (который применяется для любых атрибутов, а не только для версии).

Import-Package определяет, какая именно версия (или атрибут) ему нужна, и должен существовать соответствующий Export-Package с таким же атрибутом.

Что произойдет, если у вас есть сценарий, в котором Bundle A импортирует пакет, и в нем указывается версия, предоставляемая двумя пакетами:

Bundle-Name: Bundle A
Import-Package: org.apache.foo;version="1.2.0"

Bundle-Name: Bundle B
Export-Package: org.apache.foo;version="1.2.0"

Bundle-Name: Bundle C
Export-Package: org.apache.foo;version="1.2.0"

Какой пакет использует Bundle A?
Ответ зависит от того, какой пакет (B или C) был установлен первым.

Пакеты, установленные первыми, используются для удовлетворения зависимости при обнаружении нескольких пакетов с одинаковой версией.

Ситуация может немного усложниться при горячем развертывании пакетов после того, как некоторые из них уже решены. Что, если вы сначала установите Пакет B, а затем попробуйте установить Пакет A и следующий Пакет D вместе:

Bundle-Name: Bundle D
Export-Package: org.apache.foo;version="1.3.0"

Как мы видели выше, объявление версии в Пакете A (1.2.0) означает минимальную версию 1.2.0; так что, если бы была доступна более высокая версия, она бы выбрала это (в данном случае это версия 1.3.0 из Bundle D). Однако это приводит нас к другому временному правилу для разрешения пакета:

Пакеты, которые уже были разрешены, имеют более высокий приоритет, чем те, которые не были разрешены

Причиной этого является то, что инфраструктура OSGI имеет тенденцию способствовать многократному использованию для данного пакета. Если это решено, и новые пакеты нуждаются в этом, то он не будет пытаться иметь много других версий того же самого пакета, если это не нужно.

Bundle «использует» директиву

Приведенных выше правил для разрешения пакета все еще недостаточно, и неправильный класс может все еще использоваться во время выполнения, приводя к исключению приведения класса или подобному. Вы видите, что может отсутствовать?
Что если бы у нас был этот сценарий. Bundle A экспортирует пакет org.apache.foo , который содержит класс FooClass. FooClass имеет метод, который возвращает объект типа BarClass, но BarClass не определен в пространстве классов пакета, он импортируется следующим образом:
1
2
3
public class FooClass {
    public BarClass execute(){ ... }
}

Bundle-Name: Bundle A
Import-Package: org.apache.bar;version="3.6.0"
Export-Package: org.apache.foo;version="1.2.0"

Пока все в порядке, пока есть другой пакет, который правильно экспортирует org.apache.bar с правильной версией.

Bundle-Name: Bundle B
Export-Package: org.apache.bar;version="3.6.0"

Эти две связки разрешат нормально. Теперь, если мы установим еще два пакета, Bundle C и Bundle D, которые выглядят так:

Bundle-Name: Bundle C
Import-Package: org.apache.foo;version="1.2.0", org.apache.bar;version="4.0.0"

Bundle-Name: Bundle D
Export-Package: org.apache.bar;version="4.0.0"

Мы видим, что Bundle C импортирует пакет org.apache.foo из Bundle A. Bundle C может попытаться использовать FooClass из org.apache.foo, но когда он получит возвращаемое значение, типа BarClass, что произойдет? Пакет A предполагает использовать версию 3.6.0 BarClass, но пакет C использует версию 4.0.0. Таким образом, используемые классы не согласованы в пакетах во время выполнения (т. Е. Вы можете столкнуться с некоторым типом несоответствия или исключения приведения класса), но все будет исправляться во время развертывания, следуя приведенным выше правилам. Нам нужно сообщить любому, кто импортирует org.apache.foo, что мы используем классы из определенной версии org.apache.bar , и если вы хотите использовать org.apache.foo, вы должны использовать ту же версию, которую мы импортируем. Это именно то, что делает директива use. Давайте изменим пакет A, чтобы указать именно это:

Bundle-Name: Bundle A
Import-Package: org.apache.bar;version="3.6.0"
Export-Package: org.apache.foo;version="1.2.0"";uses:=org.apache.bar

Учитывая новую конфигурацию для пакета A, пакеты не будут корректно разрешены сверху. Пакет C не может быть разрешен, потому что он импортирует org.apache.foo, но ограничение «использует» для Пакета A указывает, что C должен использовать ту же версию, что и A (3.6.0) для org.apache.bar , в противном случае пакет будет не решить при попытке развернуть. Решением этой проблемы является изменение версии в Bundle C для org.apache.bar на 3.6.0.

Использование Apache Karaf

Контейнер OSGI Karaf основан на ядре Apache Felix , хотя ядро Equinox можно заменить при желании. Karaf является полнофункциональным контейнером OSGI и является краеугольным камнем контейнера интеграции Apache ServiceMix . ServiceMix в основном является Karaf, но специально настроен для Apache Camel, Apache ActiveMQ и Apache CXF.
Этот урок потребует Maven и Karaf. Загрузите maven с веб-сайта maven. Загрузите и установите karaf, как описано в руководстве по началу работы на веб-сайте Karaf. Вам также понадобится код, который сопровождает этот пример. Вы можете получить его в моем репозитории GitHub. После получения обязательно запустите ‘mvn install’ из проекта верхнего уровня. Это соберет и установит все пакеты в ваш локальный репозиторий maven. Несмотря на то, что вы можете устанавливать пакеты несколькими способами, использовать maven проще всего. Обратите внимание, что этот пример кода в основном состоит из имен пакетов без каких-либо реальных классов Java (кроме тех случаев, где указано в руководстве).

Первое, что нужно сделать, это запустить Караф. В обычном дистрибутиве не должно быть никаких пакетов. Проверьте это, выполнив «osgi: list» в командной строке karaf. Собираясь по порядку, мы опробуем приведенные выше правила.

Для каждого объявления пакета Import-Package должен быть соответствующий Export-Package с тем же пакетом

Чтобы проверить это правило, давайте установим Bundle A из наших примеров пакетов. Пакет A определяет пакет импорта пакета «org.apache.foo». Согласно первому правилу этот пакет не может перейти в состояние «Разрешено», поскольку нет соответствующего пакета с «Экспорт-пакетом» org.apache.foo.
В командной строке karaf введите «osgi: install mvn: explore-bundle-resolution / bundleA / 1.0?». Это установит пакет bundleA. Теперь снова выполните «osgi: list». Вы должны увидеть установленный пакет, а в столбце «Состояние» должно быть указано «Установлен». Теперь попробуйте «osgi: resolve bundle id », где bundle id — это идентификатор, указанный в команде «osgi: list». Это попытается разрешить все зависимости пакета и перевести его в состояние «Разрешено». Это не решит, однако. Снова введите «osgi: list», чтобы увидеть состояние пакета. Он все еще находится в состоянии «Установлено», хотя мы попросили OSGI разрешить его. Давайте выясним, почему. Выполните «идентификатор пакета заголовков osgi:». Под Import-Package вы должны увидеть имя пакета org.apache.foo, выделенное красным цветом. Эта зависимость отсутствует, поэтому давайте добавим ее. Введите «osgi: install -s mvn: explore-bundle-resolution / bundleB / 1.0?». Обратите внимание на ключ -s в команде. Это говорит OSGI запустить пакет после его установки. Теперь снова введите команду osgi: resol (с соответствующим идентификатором пакета). Это теперь решит пакет.

Import-Package определяет, какая именно версия (или атрибут) ему нужна, и должен существовать соответствующий Export-Package с таким же атрибутом.

Давайте установим пакет C: «osgi: install -s mvn: explore-bundle-resolution / bundleC / 1.0? Снова перечислите пакеты, и вы увидите, что хотя пакет C зависит от org.apache.foo, он указывает Import-Package с определенной версией = 1.5. Не существует разрешенной версии 1.5, поэтому пакет C также не будет разрешен. Пакет D происходит для экспорта пакета org.apache.foo с версией, равной 1,5. Установите пакет D так же, как мы установили остальные, используя -s для его запуска. Теперь попробуйте разрешить пакет C, и он должен работать («osgi: разрешить идентификатор пакета »).

Пакеты, установленные первыми, используются для удовлетворения зависимости при обнаружении нескольких пакетов с одинаковой версией.

Это правило гласит, что если существует несколько пакетов, экспортированных с одной и той же версией, OSGI выберет первый установленный пакет для использования при попытке разрешить пакеты, которые импортируют пакет. Продолжая предыдущий пример, в котором мы установили пакет C и D… рассмотрим, что пакет D экспортирует org.apache.foo; версия = 1.5. Поэтому, если мы установим пакет F, который экспортирует точно такой же пакет и версию, мы должны увидеть, что пакет C разрешается с помощью пакета из пакета D, а не из пакета F. Давайте посмотрим .. установить пакет F: «osgi: install -s mvn: исследовать-расслоение разрешение / bundleF / 1,0 ?. Сделайте список osgi: и убедитесь, что оба комплекта D и F установлены правильно и активны. Это отличная особенность OSGI: мы можем одновременно развернуть несколько версий одного и того же пакета (включая в этом примере точно такую ​​же версию). Теперь нам нужно удалить пакет C и переустановить его, чтобы увидеть, какой пакет он использует для разрешения импорта org.apache.foo. Попробуйте запустить «osgi: uninstall bundle id », чтобы удалить пакет C. Теперь переустановите его, используя команду сверху. Следует разрешить использовать комплект D. Для проверки используйте «package: import bundle id ». Вы можете попробовать переключить вещи, чтобы получить разрешение F. Возможно, вам придется использовать «osgi: refresh» для обновления пакетов OSGI.

Пакеты, которые уже были разрешены, имеют более высокий приоритет, чем те, которые не были разрешены

В некотором смысле, мы уже видели это с предыдущим правилом, но это правило вступает в силу при горячем развертывании. Это оставлено в качестве упражнения для читателя, так как этот пост уже становится довольно длинным, и я хотел бы рассказать о директиве «использование» далее.

Bundle «использует» директиву

Директива «использует» добавляет одно из последних правил и ограничений, чтобы избежать исключений приведения классов во время выполнения. Чтобы смоделировать, как работает директива «using», мы установим пакеты G, H, I и J и заметим, как контейнер применяет директиву «using».
Bundle G представляет собой своего рода «сервисный» модуль, который клиентские модули могут вызывать, чтобы «выполнить» некоторую форму обработки и вернуть результат. Результат, который он возвращает, является объектом типа BarClass, полученным из Bundle H. Но если клиент делает вызов для пакета G, он также должен использовать BarClass из пакета H, иначе это приведет к исключению приведения класса. В наших примерах Bundle I является клиентским кодом, а Bundle J представляет другую версию BarClass. Устанавливайте пакеты в любом порядке, который вам нравится, но моя демонстрация следовала этому порядку: J, H, G, I. Обратите внимание, что версия org.apache.bar действительно является версией 2.0.0, которая поставляется из пакета H, даже если пакет H был установлен второй (вопреки правилу выше). Это связано с тем, что в пакете G указана директива «users», которая зависит от конкретной версии org.apache.bar.

Ссылка: Понимание того, как пакеты OSGI решаются от нашего партнера JCG Кристиана Поста в блоге