пятница, 20 марта 2009 г.

Введение в OSGi. Взаимодействие бандлов. Зависимости.


Здравствуйте, уважаемые читатели. Суровый челябинский программист снова здесь и готов продолжить серию статей, представляющих собой введение в замечательное средство построение модульных систем - OSGi.

В прошлый раз мы с вами научились управлять жизненным циклом бандлов из консоли, а также познакомились со средствами Equinox, такими как org.eclipse.equinox.launcher.

Сегодня я предлагаю поговорить о взаимодействии бандлов. Сначала позволю себе немного пофилософствовать.

Понятно, что раз приложение многомодульное, то априори подразумевается какое-то взаимодействие этих самых модулей. И тут разумно поставить 2 вопроса: зачем и как? Ответ на вопрос "зачем" интуитивно понятен. Мы хотим, чтобы наше приложение было расширяемым, т.е. чтобы мы сами или сторонние разработчики могли изменять/наращивать его функционал. Здесь уместно ввести понятие "точка расширения". Точкой расширения (не в терминах Eclipse RCP, а в неком глобальном смысле) мы будем называть то место, в котором приложение позволяет нарастить свой функционал. Например, у нас есть главное меню приложения, в котором определены точки расширения для подменю и пунктов подменю. Это значит, что мы можем добавлять свои подменю, состоящие из пунктов меню. Но не можем, используя данные точки расширения, добавить новое окно. Переходя к бандлам видим, что концептуально здесь все просто: один бандл выставляет (публикует) некие точки расширения, а другой, подключаясь к ним, расширяет возможности первого бандла. Соответственно, третий бандл может еще больше расширить возможности первого и т.д.

Другим интересным вопросом является вопрос "как". Т.е. как определить точку расширения в одном бандле и как ее использовать в другом бандле?

OSGi предлагает несколько способов организации взаимодействия бандлов:

  1. Обмен ресурсами. Под ресурсами здесь подразумеваются java-классы, java-пэкеджи, библиотеки и другие файлы. В Eclipse RCP, например, самым распространенным способом определения точек расширения является их регистрация в файлах plugin.xml, впрочем, об этом мы еще поговорим.

  2. OSGi-сервисы. Собственно, OSGi и задумывалась, как среда выполнения и взаимодействия сервисов. Суть очень проста - один бандл выставляет сервисы, а другие их используют. Данный способ требует активации бандла, потому что выставление и подключение к сервисам, как правило, осуществляется в активаторе бандла. Активация бандла требует расходов на управление жизненным циклом бандлов, но в тоже время обеспечивает динамичность всей системы.

  3. Декларативные сервисы. Некое объединение преимуществ первого и второго способов. Сервисы описываются декларативно в xml-файлах и становятся видимыми другим бандлам. При первом использовании сервиса стартует активатор бандла, сервис становится работоспособным и доступным для использования.

  4. Регистрация событий и их обработчиков. В общем - стандартная событийная модель. Одни бандлы регистрируют события, а другие подписываются на эти события. Соответственно при возникновении нужного события в одном бандле срабатывает его обработчик из другого.




В данной заметке подробно рассмотрим обмен ресурсами между бандлами. Давайте напишем систему состоящую из двух бандлов. Первый бандл - org.beq.equinox.menu - будет представлять собой простое меню, состоящее из массива элементов, реализующих интерфейс IMenuItem. Сам интерфейс будет предоставлять бандл org.beq.equinox.menuitemizer.

Код интерфейса IMenuItem будет следующим:

package org.beq.equinox.item;



public interface IMenuItem

{

    public String getTitle();



    public String getCode();

}


Напомню, что данный интерфейс находится в бандле org.beq.equinox.menuitemizer. Собственно, больше кода в этом бандле не будет. Но помимо кода бандл должен иметь манифест. Манифест бандла org.beq.equinox.menuitemizer будет вот таким:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Menuitemizer Plug-in

Bundle-SymbolicName: org.beq.equinox.menuitemizer

Bundle-Version: 1.0.0

Require-Bundle: org.eclipse.osgi;bundle-version="3.4.0"

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Export-Package: org.beq.equinox.item

 


В принципе версия бандла org.eclipse.osgi от которого зависит данный не критична.

Теперь рассмотрим бандл org.beq.equinox.menu. Он содержит в себе java-класс Menu. Код класса Menu следующий:

package org.beq.equinox.menu;



import org.beq.equinox.item.IMenuItem;



public class Menu

{

    private static IMenuItem[] items = {

        new IMenuItem()

        {

            @Override

            public String getCode()

            {

                return "FIRST";

            }



            @Override

            public String getTitle()

            {

                return "First element";

            }

        },



        new IMenuItem() {

            @Override

            public String getCode()

            {

                return "SECOND";

            }



            @Override

            public String getTitle()

            {

                return "Second element";

            }

        }

    };



    public IMenuItem[] getItems()

    {

        return items;

    }

}

 


Больше java-кода в бандле не будет. Теперь создадим манифест бандла следующего содержания:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Menu Plug-in

Bundle-SymbolicName: org.beq.equinox.menu

Bundle-Version: 1.0.0

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Import-Package: org.beq.equinox.item

 


Как видим мы явно указали директиву Import-Package. Если не указать подобную директиву - Eclipse PDE не даст собрать бандл. Однако, если обойти эту неприятность - проблема все равно возникнет в рантайме, при использовании кода.

Чтож, пришло время развернуть нашу любимую Equinox-консоль и инсталлировать наши бандлы!

Выполняем команду:


java -jar org.eclipse.osgi_3.4.0.v20080605-1900.jar -console


Посмотрим статус Equinox с помощью команды ss:



Установим бандл org.beq.equinox.menu с помощью команды install:



Бандл находится в состоянии INSTALLED. Это обозначает, что не все зависимости бандла разресолвины и такой бандл нельзя будет стартовать (выполнить активатор, перевести в состояние ACTIVE). Даже если не предполагается работа бандла в состоянии ACTIVE нересолвинг зависимостей чреват ошибками в рантайме.

Посмотрим диагностику по бандлу:


diag 1




Как видим импортируемый нами пакет org.beq.equinox.item недоступен бандлу. Кстати, пакеты тоже имеют версионность.

Есть 2 способа декларировать импорт пакетов из бандла - использовать директиву Import-Package или использовать директиву Require-Bundle. Директива Import-Package требует перечисления всех пэкеджей, доступных бандлу, а их может быть очень много. Поэтому на мой взгляд грамотно - перечислить бандлы, от которых зависит данный, используя директиву Require-Bundle. Впрочем вот здесь приводится более детальное сравнение двух этих стратегий.

Посмотрим на поведение нашего бандла в случае использования директивы Require-Bundle. Сначала изменим манифест бандла следующим образом:

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Menu Plug-in

Bundle-SymbolicName: org.beq.equinox.menu

Bundle-Version: 1.0.0

Bundle-RequiredExecutionEnvironment: JavaSE-1.6

Require-Bundle: org.beq.equinox.itemizer


Стартуем Equinox-консоль, инсталлируем бандл. Смотрим его статус:




Теперь надо разресолвить зависимости. Устанавливаем бандл org.beq.equinox.menuitemizer.



Обновляем состояние бандла org.beq.equinox.menu с помощью команды refresh 1. Смотрим статус Equinox:



Ву-а-ля. Зависимости полностью разресолвены, бандлы готовы к использованию.

А что делать в случае, если бандл экспортирует библиотеку? Увы, придется экспортировать все пэкеджи этой библиотеки.

В следующей заметке мы поговорим об использовании более сложного и гибкого способа организации взаимодействия бандлов - об использовании OSGi-сервисов. Оставайтесь на связи!

Суровый челябинский программист всегда будет рад вашим вопросам. Задавайте их в комментариях.

З.Ы. Как вы считаете - следует ли мне продолжить практику кроспостинга моих статей на ХабраХабр?

Понравилось сообщение - подпишись на блог

4 комментария:

Анонимный комментирует...

С удовольствием читаю статьи по данной теме на русском языке, это очень радует.. Относительно недавно (в конце 2008 г.), я заинтересовался платформой Eclipse и все что с ней связано, мне показалось это очень интересным и я приступил к изучению. В связи с тем, что английский мой не на высшем уровне, статьи читались не так быстро как хотелось.. Ваш материал читается гораздо быстрее)) и с большим удовольствием..
Спасибо Вам!!

Samolisov Pavel комментирует...

Большое спасибо за столь приятный комментарий. У самого с английским тоже не ахти, но вот пытаюсь читать доку и списки рассылки eclipse-разработчиков. Правда, все что прочитал стараюсь проверять на несложных примерах. Результатом и являются вот-таки заметки.

Alexander Lipatov комментирует...

Хороший пост, все описанное в нем проделал, доволен.

По поводу Require-Bundle и Import-Package: благодаря той ссылке, что Павел давал в статье, я понял, что нужно стараться везде использовать Import-Package. Потому, что Require-Bundle тащит за собой гораздо больше ненужных зависимостей, и если бандл разделят на части, все сломается. Да, еще в бандле могут пакет переименовать или вообще убрать... Import-Package рулит не по детски!

Samolisov Pavel комментирует...

Не все так однозначно, о чем собственно и написано по ссылке. Если вы пишите плагин для эклипс - то может быть и есть риск, что нужный вам пэкедж переименуют. Если вы разрабатываете свое приложение (пусть и в команде) то тут уже можно договориться. Так же важно колличество импортируемых пэкеджей. Если каждый нужный мне бандл экспортирует около 10-ка нужных мне пэкеджей, то я лучше буду использовать Require-Bundle.

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!