Статьи

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

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

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

Жизненный цикл пакета OSGIопределяет возможные состояния и переходы для расслоения. Мы будем обсуждать состояние «Разрешено» пакета, что означает состояние, которого он может достичь после «Установки» и когда все его необходимые зависимости удовлетворены. Традиционная загрузка классов Java восприимчива к исключениям ClassCastException во время выполнения, когда два класса с одним и тем же полностью определенным именем из двух разных загрузчиков классов смешиваются и один используется в неправильном пространстве пути к классам. Одна из основных целей OSGI состоит в том, чтобы избежать такого рода исключений во время выполнения путем разрешения всех зависимостей во время развертывания, так как идея «быстрого» отказа во время развертывания будет легче отлаживать, чем пытаться отследить проблемы загрузки классов во время выполнения. Подумайте о том, как раздражают некоторые классы, которые не найдены, или исключения приведения классов, например, для отладки в развертывании Weblogic. OSGI решает это.Чтобы пакет достиг состояния «Разрешено», у него должны быть выполнены зависимости. Подумайте о «быстром отказе» в подходе к разрешению пакетов следующим образом: если вы используете весеннее приложение, и один из ваших компонентов не может быть подключен должным образом из-за отсутствия определения компонента, вы узнаете об этом во время развертывания, а не когда клиент вызывая ваш код. Тот же принцип применяется с OSGI; вместо зависимостей проводки на уровне объекта мы будем связывать зависимости модуля и загрузки классов.Тот же принцип применяется с 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; версия = «1.2.0»

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

Bundle-Name: Bundle B
Export-Package: org.apache.foo; версия = «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; версия = «1.2.0»

Bundle-Name: Bundle B
Export-Package: org.apache.foo; версия = «1.2.0»

Bundle-Name: Bundle C
Export-Package: org.apache.foo; версия = «1.2.0»

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

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

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

Bundle-Name: Bundle D
Export-Package: org.apache.foo; версия = «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 не определен в пространстве классов пакета, он импортируется следующим образом:

public class FooClass {
    public BarClass execute(){ ... }
}

Bundle-Name: Bundle A
Import-Package: org.apache.bar; версия = «3.6.0»
Export-Package: org.apache.foo; версия = «1.2.0»

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

Bundle-Name: Bundle B
Export-Package: org.apache.bar; версия = «3.6.0»

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

Bundle-Name: Bundle C
Import-Package: org.apache.foo; версия = «1.2.0», org.apache.bar; версия = «4.0.0»

Bundle-Name: Bundle D
Export-Package: org.apache.bar; версия = «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Вы должны использовать ту же версию, что мы импортируем. Это именно то , что использование делает директивы. Давайте изменим пакет A, чтобы указать именно это:

Bundle-Name: Bundle A
Import-Package: org.apache.bar; версия = «3.6.0»
Export-Package: org.apache.foo; версия = «1.2.0» «; использует: = 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.

Если мои примеры не были ясны или есть какое-то недопонимание, пожалуйста, оставьте комментарий.