410013796724260
• Webmoney
R335386147728
Z369087728698
Cервисы взаимодействия бандловМодульная система, которая поддерживает только статические зависимости, является хрупкой и не может поддерживать расширяемость. Хрупкость означает, что нельзя удалить какой-либо модуль, не нарушая работоспособности (компиляцию, сборку) приложения в целом. Отсутствие расширяемости означает, что нельзя добавить к системе новый функциональный модуль без перекомпиляции существующей системы. Одна из самых важных задач объектно-ориентированного программирования связана с повышением гибкости и повторным использованием кода за счет уменьшения связи между поставщиками определенной функциональности и потребителями этой функциональности. В Java это достигается за счет использования интерфейсов, которые представляют чисто абстрактные классы, описывающие функциональность в виде определенного списка методов других классов, реализующих их. Интерфейсы и абстрактные классы можно использовать в качестве ссылок на определенный объект при позднем связывании для построении модульной системы. Позднее связываниеOSGi решает проблему «позднего связывания» использованием динамических сервисов, в основе которых лежит принцип внедрения зависимости Dependency injection (DI) — процесс предоставления внешней зависимости программному компоненту. DI является специфичной формой «инверсии управления» (Inversion of control, IoC), когда она применяется к управлению зависимостями. В этом случае объект (bundle) перекладывает ответственность за построение требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму. Следует вспомнить, что в спецификации OSGi оперируют так называемыми бандлами (bundle). В тексте статьи наряду с бандлами можно встретить также и такую сущность, как плагин, значение которого отнесем к синониму. На следующем рисунке представлена схема взаимодействия бандлов в OSGi контейнере с использованием сервисов. Предоставляющий определенные услуги bundle должен быть зарегистрирован как сервис (Registr) в реестре контейнера OSGi. Остальные бандлы (client) могут получить на него «ссылку» (Find). После этого необходимо с сервисом связаться (Bind) и использовать его услуги (методы). Здесь следует обратить внимание на особенности использования данной схемы взаимодействия :
Если bundle-сервис независим от других сервисов, то его разработка упрощается : необходимо соответствующим образом определить структуру и методы, после чего инсталировать в контейнер OSGi и зарегистрировать в качестве сервиса. А вот с bundle-клиентом задача немного по-сложнее, поскольку уже при разработке необходимо использовать методы bundle-сервис. Посмотрим, как это будет выглядеть на примере. Пример использования OSGi сервиса для взаимодействия бандловРассмотрим пример взаимодействия двух бандлов. Один bundle (сервис) будет предоставлять список валют, а другой bundle (клиент) будет подключаться к сервису, получать список валют и выводить его в консоль. При разработке будут созданы два maven-проекта :
Описание bundle согласно спецификации OSGi и его отличие от обычного jar-файла подробно описано здесь. Описание проекта service-currencyСтруктура сервиса service-currency включает интерфейс ICurrency.java, реализацию CurrencyImpl.java и активатор CurrencyActivator.java. Листинг интерфейса ICurrency.javapublic interface ICurrency { public String getName(); } Интерфейс включает один метод getName(), возвращающий наименование сервиса. Листинг класса CurrencyImpl.javapublic class CurrencyImpl implements ICurrency { public String getName() { return CurrencyImpl.class.getName(); } public String[] listCurrencies() { return new String[] {"rub", "usd", "eur"}; } } Класс CurrencyImpl реализует метод getName() интерфейса ICurrency и включает дополнительно метод listCurrencies(), возвращающий список валют в виде массива строк. Листинг активатора сервиса CurrencyActivator.javaimport java.util.Properties; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceRegistration; public class CurrencyActivator implements BundleActivator { ServiceRegistration sr; public void start(BundleContext context) throws Exception { // создание класса реализации сервиса ICurrency service = new CurrencyImpl(); // определение свойств сервиса Properties props = new Properties(); props.put("service", "currency"); // регистрация сервиса sr = context.registerService(service.getName(), service, props); System.out.println("start service <currency>"); } public void stop(BundleContext context) throws Exception { sr.unregister(); System.out.println("stop service <currency>"); } } Активатор сервиса при старте (метод start) получает в качестве параметра контекст контейнера context типа BundleContext, в котором регистрирует сервис вызовом метода контекста registerService. Метод registerService возвращает объект типа ServiceRegistration, который используется при останове сервиса в методе stop. В качестве параметров для регистрации сервиса в метод registerService передаются наименование сервиса (класса), экземляр сервиса и свойства. Свойства сервиса можно не определять, тогда в метод нужно передать значение null. В методе stop освобождаются ресурсы сервиса (bundle) и отменяется регистрация сервиса вызовом метод unregister класса ServiceRegistration. Описание сборки pom.xmlДля сборки проекта service-currency используется pom.xml следующего содержания : <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.osgi.service</groupId> <artifactId>service-currency</artifactId> <packaging>bundle</packaging> <version>1.0.0</version> <name>Service currency bundle</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding> UTF-8 </project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.osgi.core</artifactId> <version>1.4.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.3.7</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName> ${project.groupId}.${project.artifactId} </Bundle-SymbolicName> <Bundle-Name> ${project.name} </Bundle-Name> <Bundle-Version> ${project.version} </Bundle-Version> <Bundle-Localization> plugin </Bundle-Localization> <Bundle-Activator> ${project.groupId}.CurrencyActivator </Bundle-Activator> <Import-Package> org.osgi.framework;version="1.4.0" </Import-Package> <Export-Package> ${project.groupId} </Export-Package> </instructions> </configuration> </plugin> </plugins> </build> </project> В файле описания сборки pom.xml определяется зависимость фреймворка osgi (секция <dependency>) и параметры bundle для формирования манифеста META-INF/MANIFEST.MF. В результате сборки в поддиректории проекта target будет создан bundle в виде файла service-currency-1.0.0.jar с манифестом следующего содержания : Manifest-Version: 1.0 Bnd-LastModified: 1487417732151 Build-Jdk: 1.7.0_67 Bundle-Activator: com.osgi.service.CurrencyActivator Bundle-Localization: plugin Bundle-ManifestVersion: 2 Bundle-Name: Service currency bundle Bundle-SymbolicName: com.osgi.service.service-currency Bundle-Version: 1.0.0 Created-By: Apache Maven Bundle Plugin Export-Package: com.osgi.service;uses:="org.osgi.framework";version="1.0.0" Import-Package: org.osgi.framework;version="1.4.0" Tool: Bnd-1.50.0 Инсталляция и регистрация сервисаДля тестирования созданного сервиса используем включенный в архив примера папку <osgi.container>, в которой размещается org.eclipse.osgi_ХХХ.jar для создания контейнера Equinox. О командах работы в контейнере можно прочитать здесь. Запустим контейнер Equinox, инсталлируем в контейнер bundle-сервис и стартуем его : java -jar org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console osgi> ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 osgi> install file:/d:/osgi.container/bundles/service-currency-1.0.0.jar Bundle id is 1 osgi> ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 1 INSTALLED com.osgi.service.service-currency_1.0.0 osgi> start 1 start service <currency> osgi> ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 3 ACTIVE com.osgi.service.service-currency_1.0.0 osgi> services (objectClass=com.osgi.service.CurrencyImpl) {com.osgi.service.CurrencyImpl}={service=currency, service.id=29} Registered by bundle: com.osgi.service.service-currency_1.0.0 [1] No bundles using service. osgi> stop 1 stop service <currency> osgi> ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 3 RESOLVED com.osgi.service.service-currency_1.0.0 В процессе тестирования bundle-сервиса мы проводим его через все стадии жизненного цикла выполнением команд install, start, stop. Командой services с соответствующим параметром в консоль была выведена информация о сервисе :
Примечание : для просмотра конкретного сервиса можно использовать маску в виде символа *, чтобы не писать полный путь (пакет, наименование), т.е. наш сервис можно было бы вывести в консоль командой osgi> services (objectClass=*CurrencyImpl). Если вызвать команду services без параметров, то можно будет увидеть весь список сервисов, загруженных контейнером Equinox. Таким образом, можно заключить, что сервис успешно загружен и зарегистрирован в системе, но никто к нему не подключен. Описание проекта bundle-exchangeСтруктура плагина bundle-exchange включает интерфейс IExchange.java, реализацию ExchangeImpl.java и активатор ExchangeActivator.java. Листинг интерфейса IExchange.javaimport java.util.List; public interface IExchange { public List<String> getCurrencies(); } Интерфейс включает один метод getCurrencies(), возвращающий список текстовых значений. Листинг класса ExchangeImpl.javaimport java.util.List; import java.util.Arrays; import java.util.ArrayList; import org.osgi.util.tracker.ServiceTracker; import com.osgi.service.CurrencyImpl; public class ExchangeImpl implements IExchange { private final ServiceTracker<Object, Object> serviceTracker; public ExchangeImpl (ServiceTracker<Object, Object> tracker) { serviceTracker = tracker; } public List<String> getCurrencies() { CurrencyImpl service = (CurrencyImpl)serviceTracker.getService(); if (service == null) { System.out.println ("service is null"); return new ArrayList<String>(); } return Arrays.asList(service.listCurrencies()); } } При доступе к сервису в методе getCurrencies() выполняется проверка на null. Если сервис не найден, то метод вернет пустой список. Это может быть связано с тем, что сервис может отсутствовать или не быть зарегистрированным в системе. В этом случае на попытку получить сервис, ServiceTracker вернет нам null. Утилитный класс OSGi ServiceTracker передается в качестве параметра в конструктор и используется для получения зарегистрированного в системе сервиса. C помощью ServiceTracker можно : Конструкторы объекта ServiceTrackerServiceTracker(BundleContext, ServiceName , ServiceTrackerCustomizer); ServiceTracker(BundleContext, Filter , ServiceTrackerCustomizer); ServiceTracker(BundleContext,ServiceReference, ServiceTrackerCustomizer); Каждый из конструкторов принимает в качестве параметра BundleContext. Первый конструктор используется для получения имени интерфейса сервиса в качестве критерия поиска. Второй конструктор используется для отслеживания всех сервисов, подпадающих под указанный фильтр. Третий конструктор принимает в качестве аргумента ServiceReference, который позволяет отслеживать конкретный сервис. Как только ServiceTracker создан, он начинает отслеживать сервисы и позволяет использовать следующие свои методы :
Примечание : не рекомендуется использовать getServiceReference()/getServiceReferences() в активаторах бандла, так как предполагается, что активаторы бандла должны быстро загружаться, а фреймворк может приостановить выполнение активатора до загрузки бандла, загружающего нужный сервис, что может привести к deadlock-у, если сервис не будет загружен. Листинг активатора ExchangeActivator.javaimport org.osgi.framework.BundleContext; import org.osgi.framework.BundleActivator; import org.osgi.util.tracker.ServiceTracker; import com.osgi.service.CurrencyImpl; public class ExchangeActivator implements BundleActivator { private ServiceTracker<Object, Object> tracker; public void start(BundleContext context) throws Exception { System.out.println("start bundle exchange"); // создание и открытие трекера tracker = new ServiceTracker<Object, Object>( context, CurrencyImpl.class.getName(), null); tracker.open(); ExchangeImpl list = new ExchangeImpl(tracker); // вывод в консоль списка валют System.out.println("currencies list"); for (String item : list.getCurrencies()) System.out.println(item); } public void stop(BundleContext context) throws Exception { System.out.println("stop bundle exchange"); tracker.close(); } } Для создания трекера при старте в конструктор ServiceTracker вторым параметром передается наименование сервиса. После этого трекер открывается, создается экземпляр бандла и в консоль выводится список валют. В методе stop вместе с бандлом закрывается и трекер. Описание сборки pom.xml<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.osgi.bundle</groupId> <artifactId>bundle-exchange</artifactId> <packaging>bundle</packaging> <version>1.0.0</version> <name>Bundle exchange Plug-in</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding> UTF-8 </project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.osgi.core</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.util.tracker</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>com.osgi.service</groupId> <artifactId>service-currency</artifactId> <version>1.0.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.3.7</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName> ${project.groupId}.${project.artifactId} </Bundle-SymbolicName> <Bundle-Name> ${project.name} </Bundle-Name> <Bundle-Version> ${project.version} </Bundle-Version> <Bundle-Activator> ${project.groupId}.ExchangeActivator </Bundle-Activator> <Private-Package> ${project.groupId} </Private-Package> </instructions> </configuration> </plugin> </plugins> </build> </project> В файле описания сборки pom.xml определяется зависимость плагина от артефакта service-currency на время разработки (provided), необходимая для компиляции и создания bundle. Чтобы maven нашел данный артефакт в репозитории необходимо выполнить в предыдущем проекте service-currency команду mvn install, которая загрузит артефакт в репозиторий. В результате сборки в поддиректории проекта target будет создан bundle в виде файла bundle-exchange-1.0.0.jar с манифестом следующего содержания : Manifest-Version: 1.0 Bnd-LastModified: 1487435015998 Build-Jdk: 1.7.0_67 Bundle-Activator: com.osgi.bundle.ExchangeActivator Bundle-ManifestVersion: 2 Bundle-Name: Bundle exchange Plug-in Bundle-SymbolicName: com.osgi.bundle.bundle-exchange Bundle-Version: 1.0.0 Created-By: Apache Maven Bundle Plugin Export-Package: com.osgi.bundle;uses:="com.osgi.service, org.osgi.util.tracker,org.osgi.framework";version="1.0.0" Import-Package: com.osgi.service;version="[1.0,2)",org.osgi.framework; version="[1.5,2)",org.osgi.util.tracker;version="[1.5,2)" Tool: Bnd-1.50.0 ТестированиеКак было отмечено выше, в архив примеров входит папка «osgi.container», в которой размещается org.eclipse.osgi_ХХХ.jar для создания контейнера Equinox. Для автоматической загрузки плагинов (бандлов) их необходимо разместить в поддиректории «osgi.container/bundles», а в поддиректории «osgi.container/configuration» настроить файл инициализации config.ini. После этого бандлы будут автоматически загружены в контейнер, сервис будет зарегистрирован и к нему подключится клиент. А в консоли мы увидим следующую «картину» : java -jar org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console osgi> start service <currency> start bundle exchange currencies list rub usd eur ss Framework is launched. id State Bundle 0 ACTIVE org.eclipse.osgi_3.7.1.R37x_v20110808-1106 1 ACTIVE com.osgi.service.service-currency_1.0.0 2 ACTIVE com.osgi.bundle.bundle-exchange_1.0.0 osgi> services (objectClass=com.osgi.service.CurrencyImpl) {com.osgi.service.CurrencyImpl}={service=currency, service.id=29} Registered by bundle: com.osgi.service.service-currency_1.0.0 [1] Bundles using service: com.osgi.bundle.bundle-exchange_1.0.0 [2] Можно сказать поставленная задача решена и бандлы «нашли друг друга» на просторах контейнера Equinox. Скачать исходный кодИсходные коды рассмотренного примера взаимодействия bundle для платформы OSGi в виде проектов Eclipse можно скачать здесь (1.2 Мб). В архив проекта дополнительно включена директория osgi.container для создания контейнера Equinox и тестирования взаимодействия бандлов. Пример реализации механизма publish/subscribe (публикации/подписки) в OSGi приложения с использованием фреймворка Felix представлен здесь. Примеры взаимодействия бандлов представлены в разделе описания многомодульного OSGi-приложения на платформе JaBricks. |