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.java
public interface ICurrency
{
public String getName();
}
Интерфейс включает один метод getName(), возвращающий наименование сервиса. Листинг класса CurrencyImpl.java
public 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.java
import 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.java
import java.util.List;
public interface IExchange
{
public List<String> getCurrencies();
}
Интерфейс включает один метод getCurrencies(), возвращающий список текстовых значений. Листинг класса ExchangeImpl.java
import 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.java
import 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. |
