Механизм publish/subscribe в OSGi

При разработке бандлов для OSGi-приложений очень важное значение приобретает реализация механизма publish/subscribe (публикация и подписка). Ведь согласно спецификации OSGi бандл может ничего не знать о других модулях программы. Кроме этого, «условно-зависимые» бандлы (если можно так выразиться) могут разрабатываться и совершенствоваться разными программистами и функционал приложения может расширяться за счет установки дополнительных бандлов. К примеру, как быть, если один из бандлов определяет язык локализации интерфейса приложения, а ряд других бандлов отображают графический интерфейс согласно выбранной Locale? Аналогичная ситуация может возникнуть и с текущей валютой, значение которой может влиять на представление определенной информации в разных формах и отчетах (бандлах).

Описание бандла (bundle) согласно спецификации OSGi и его отличие от обычного jar-файла подробно описано здесь.

Механизм публикации и подписки как нельзя лучше позволяет решить описанные выше проблемы. Так бандл, управляющий определенными свойствами приложения, рассылает (публикует) сообщения при изменении этих свойств. Остальные бандлы, использующие данные свойства приложения, подписываются на рассылку этих сообщений. Такой подход прозволяет существенно облегчить труд программистов (не надо придумывать API), поскольку для разработки «условно-зависимых» бандлов, использующих механизм publish/subscribe необходимо только определить текст отправляемого сообщения.

Генерация событий и формат сообщения

Следует несколько слов сказать о генерации событий и формате рассылаемых сообщений. Для генерации событий фреймворк использует OSGi-сервис EventAdmin, в котором определены два метода:

  • postEvent - отправка асинхронного события;
  • sendEvent - отправка синхронного события.

Оба метода в качестве параметра получают событие Event, которое включает текстовый топик события (EVENT_TOPIC) и свойства, описывающие конкретное событие (тип Dictionary). Топик события является фильтром для диспетчера событий, который находит обработчик для конкретного события. Формат топика должна удовлетворять доменной модели с использованием в качестве разделителя символа обратного слеша "/". Например, топик события "service/event/Currency". Обработчики могут подписываться сразу на группу событий, используя в качестве последнего домена символ "*". Так топик "org/osgi/framework/BundleEvent/*" позволяет подписаться на все события, связанные с изменением состояний различных бандлов.

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

Пример использования publish/subscribe в OSGi приложении

Рассмотрим пример взаимодействия двух бандлов посредством публикации и подписки. Один bundle (bundle-exchange) подпишем на событие изменения валюты 'service/event/Currency' и дополнительно на изменение состояний бандлов 'org/osgi/framework/BundleEvent/*'. Таким образом, bundle-exchange будет контролировать как возникновение всех событий, связанных с изменением состояний бандлов, так и событий, связанных с изменением валюты. Второй бандл (bundle-publisher) будет отправлять с определенной периодичностью события 'service/event/Currency'. Дополнительно, в качестве «дирижера» используем приложение framework-pubsub, которое инсталлирует и стартует фреймворк и бандлы.

Для реализации тестового примера создаем maven-проекты. Дополнительно используем бандл org.apache.felix.eventadmin-1.4.8.jar, для отправки сообщений. В качестве фреймворка в приложении framework-pubsub используем Felix.

Описание проекта bundle-exchange

Структура проекта bundle-exchange, представленная на следующем скриншоте, включает два класса : активатор ExchangeActivator и обработчик события ExchangeSubscriber.

Листинг обработчика событий ExchangeSubscriber.java

Класс ExchangeSubscriber выводит в консоль информацию о событии.

package com.osgi.bundle;

import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;

public class ExchangeSubscriber implements EventHandler 
{
    @Override
    public void handleEvent(Event event)
    {
        System.out.println("\t! ExchangeSubscriber : " +
                    "received event on topic = " + event.getTopic());
        for (String propertyName : event.getPropertyNames())
            System.out.println("\t!\t" + propertyName + " = " + 
                    event.getProperty(propertyName));
        System.out.println("\t------------------------------------");
    }
}

Листинг активатора сервиса ExchangeActivator.java

Активатор бандла ExchangeActivator реализует методы start и stop интерфейса BundleActivator. В методе start активатор получает в качестве параметра контекст фреймворка BundleContext, в котором подписывается на два события вызовом метода контекста registerService. В качестве параметров в метод registerService передаются обработчик событий ExchangeSubscriber и свойства, включающие EVENT_TOPIC события.

В методе stop бандл освобождает ресурсы и отменяет регистрацию сервисов вызовами метода unregister класса ServiceRegistration.

package com.osgi.bundle;

import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleActivator;

import org.osgi.service.event.EventHandler;
import org.osgi.service.event.EventConstants;

import org.osgi.framework.ServiceRegistration;

public class ExchangeActivator implements BundleActivator
{
    private ServiceRegistration<?>  service_currency;
    private ServiceRegistration<?>  service_bundle  ;
    //---------------------------------------------------------------
    @Override
    public void start(BundleContext context) throws Exception
    {
        System.out.println("   start : bundle-exchange");

        ExchangeSubscriber monitor = new ExchangeSubscriber();

        Dictionary<String, Object> dict1;
        Dictionary<String, Object> dict2;
        String EVENT1 = "org/osgi/framework/BundleEvent/*";
        String EVENT2 = "service/event/Currency";
        // Подключение слушателя
        dict1 = new Hashtable<String, Object>();
        dict1.put(EventConstants.EVENT_TOPIC, EVENT1);
        service_bundle = context.registerService(
                    EventHandler.class.getName(), monitor, dict1);

        if (service_bundle != null)
            System.out.println("\nExchangeActivator : Service " +
                          "'org/osgi/framework/BundleEvent/*' " + 
                                               "is registered\n");
        else
            System.out.println("ExchangeActivator : Service 
                          "'org/osgi/framework/BundleEvent/*'
                                            "is not registered");
        // Подключение слушателя
        dict2 = new Hashtable<tring, Object>();
        dict2.put(EventConstants.EVENT_TOPIC, EVENT2);
        service_currency = context.registerService(
                     EventHandler.class.getName(), monitor, dict2);

        if (service_currency != null)
            System.out.println("\nExchangeActivator : Service " +
                                    "'service/event/Currency' " + 
                                              "is registered\n");
        else
            System.out.println("ExchangeActivator : Service "   + 
                                    "'service/event/Currency' " +
                                            "is not registered");
    }
    //---------------------------------------------------------------
    @Override
    public void stop(BundleContext context) throws Exception
    {
        if (service_currency != null) {
            service_currency.unregister();
            service_currency = null;
        }
        if (service_bundle != null) {
            service_bundle.unregister();
            service_bundle = null;
        }
        System.out.println("stop : bundle-exchange");
    }
}

Описание сборки pom.xml

Для сборки бандла bundle-exchange используется 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</name>
  <url>http://maven.apache.org</url>

  <properties>
        <project.build.sourceEncoding>
             UTF-8
        </project.build.sourceEncoding>
        <maven.test.skip>true</maven.test.skip>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  
  <dependencies>
      <dependency>
          <groupId>org.apache.felix</groupId>
          <artifactId>org.apache.felix.framework</artifactId>
          <version>5.6.1</version>
          <scope>provided</scope>
      </dependency>
      <dependency>
          <groupId>org.apache.felix</groupId>
          <artifactId>org.apache.felix.eventadmin</artifactId>
          <version>1.2.2</version>
          <scope>provided</scope>
      </dependency>
  </dependencies>
  
   <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>2.3.2</version>
              <configuration>
                  <source></source>
                  <target></target>
              </configuration>
          </plugin>
          <plugin>
              <groupId>org.apache.felix</groupId>
              <artifactId>maven-bundle-plugin</artifactId>
              <version>2.3.7</version>
              <extensions>true</extensions>
              <configuration>
                  <instructions>
                     <Bundle-SymbolicName>
                          .
                     </Bundle-SymbolicName>
                     <Bundle-Name></Bundle-Name>
                     <Bundle-Version>
                          
                     </Bundle-Version>
                     <Bundle-Activator>
                          .ExchangeActivator
                     </Bundle-Activator>
                     <Import-Package>
                          org.osgi.framework.*,
                          org.osgi.service.event.*;
                     </Import-Package>
                  </instructions>
              </configuration>
          </plugin>
      </plugins>
   </build>
</project>

В файле описания сборки pom.xml определяются зависимости фреймворка (секция <dependencies>) и параметры bundle для формирования манифеста MANIFEST.MF. Следует обратить внимание, что в теге <scope> зависимостей указано значение provided, чтобы они не были включены в бандл. Бандл должен получить классы/объекты данных зависимостей от фреймворка (см. тег Import-Package).

В результате сборки в поддиректории проекта target будет создан bundle в виде файла bundle-exchange-1.0.0.jar с манифестом следующего содержания :

Manifest-Version: 1.0
Bnd-LastModified: 1500971779957
Build-Jdk: 1.8.0_141
Built-By: EugeneMK
Bundle-Activator: com.osgi.bundle.ExchangeActivator
Bundle-ManifestVersion: 2
Bundle-Name: Bundle exchange
Bundle-SymbolicName: com.osgi.bundle.bundle-exchange
Bundle-Version: 1.0.0
Created-By: Apache Maven Bundle Plugin
Export-Package: com.osgi.bundle;uses:="org.osgi.service.event,org.osgi.
 framework";version="1.0.0"
Import-Package: org.osgi.framework;version="[1.8,2)",org.osgi.service.
 event;version="[1.2,2)"
Tool: Bnd-1.50.0

Описание проекта bundle-publisher

Структура проекта bundle-publisher, представленная на следующем скриншоте, включает активатор бандла PublisherActivator и «издателя» сообщений EventPublisher.

Листинг EventPublisher.java

Класс EventPublisher отправляет сообщения. Конструктор класса получает в качестве параметра объект EventAdmin контекста фреймворка. При вызове метода отправки события sendCurrencyEvent формируется набор свойств dict и создается событие EVENT_TOPIC. Для отправки используется метод postEvent. Вы можете проверить метод sendEvent.

package com.osgi.bundle;

import java.util.Dictionary;
import java.util.Hashtable;

import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;

public class EventPublisher 
{
    private final EventAdmin admin;
    private final String EVENT_TOPIC = "service/event/Currency";
    private final String TEMPLATE    = "bundle-publisher : \n"
                                        + "    send event '%s',"
                                        + " currency = %s"; 
    //---------------------------------------------------------------
    public EventPublisher(EventAdmin admin)
    {
        this.admin = admin;
    }
    //---------------------------------------------------------------
    public void sendCurrencyEvent(String curr)
    {
        Dictionary<String, Object> dict;
        dict = new Hashtable<String,Object>();
        dict.put("currency" , currency);
        Event event = new Event(EVENT_TOPIC, dict);
        System.out.println(String.format(TEMPLATE, EVENT_TOPIC,curr));
        admin.postEvent(event);
//      admin.sendEvent(event);
    }
}

Активатор PublisherActivator.java

Активатор бандла PublisherActivator в методе start создает «издателя» событий EventPublisher. Для создания издателя в методе createPublisher определяется ссылка ServiceReference на объект EventAdmin контекста фреймворка. При ненулевой ссылке ref извлекается объект EventAdmin из контекста фреймворка (getService), и только после этого создается объект EventPublisher. Далее активатор формирует поток, который с интервалом в 1 сек. отправляет три сообщения и завершает работу.

package com.osgi.bundle;

import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.ServiceReference;

import org.osgi.service.event.EventAdmin;

public class PublisherActivator implements BundleActivator
{
    private  EventPublisher  eventPublisher;
    private  final String[]  curencies = {"rub", "usd", "eur"};
    private  int             idx       = 0;
    //---------------------------------------------------------------
    @Override
    public void start(BundleContext context) throws Exception
    {
        System.out.println("  START : bundle-publisher");

        eventPublisher = createPublisher(context);
        if (eventPublisher != null) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            String curr = curencies[idx++];
                            eventPublisher.sendCurrencyEvent(curr);
                            if (idx > 2)
                                break;
                            Thread.sleep(1000);
                        } catch (InterruptedException ex) {}
                    }
                }
            }).start();    		
        }
    }
    //---------------------------------------------------------------
    @Override
    public void stop(BundleContext context) throws Exception
    {
        System.out.println("stop : bundle-publisher");
    }
    //---------------------------------------------------------------
    @SuppressWarnings("unchecked")
    private EventPublisher createPublisher(BundleContext context) 
    {
        EventPublisher publisher = null;
        ServiceReference<EventAdmin> ref;
        ref = (ServiceReference<EventAdmin>) context
                    .getServiceReference(EventAdmin.class.getName());
        if (ref != null) {
            EventAdmin eventAdmin;
            eventAdmin = (EventAdmin) context.getService(ref);
            if (eventAdmin != null)
                publisher = new EventPublisher(eventAdmin);
            else
                System.out.println("  ERROR : eventAdmin is null");
        } 
        return publisher;
    }
}

Описание сборки pom.xml

Для сборки проекта bundle-publisher используется 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-publisher</artifactId>
  <packaging>bundle</packaging>
  <version>1.0.0</version>

  <name>Bundle publisher</name>
  <url>http://maven.apache.org</url>

  <properties>
        <project.build.sourceEncoding>
             UTF-8
        </project.build.sourceEncoding>
        <maven.test.skip>true</maven.test.skip>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  
  <dependencies>
      <dependency>
          <groupId>org.apache.felix</groupId>
          <artifactId>org.apache.felix.framework</artifactId>
          <version>5.6.1</version>
          <scope>provided</scope>
      </dependency>
      <dependency>
          <groupId>org.apache.felix</groupId>
          <artifactId>org.apache.felix.eventadmin</artifactId>
          <version>1.2.2</version>
          <scope>provided</scope>
      </dependency>
  </dependencies>
  
   <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>2.3.2</version>
              <configuration>
                  <source></source>
                  <target></target>
              </configuration>
          </plugin>
          <plugin>
              <groupId>org.apache.felix</groupId>
              <artifactId>maven-bundle-plugin</artifactId>
              <version>2.3.7</version>
              <extensions>true</extensions>
              <configuration>
                  <instructions>
                     <Bundle-SymbolicName>
                          .
                     </Bundle-SymbolicName>
                     <Bundle-Name></Bundle-Name>
                     <Bundle-Version>
                          
                     </Bundle-Version>
                     <Bundle-Activator>
                          .PublisherActivator
                     </Bundle-Activator>
                     <Import-Package>
                          org.osgi.framework.*,
                          org.osgi.service.event.*;
                     </Import-Package>
                  </instructions>
              </configuration>
          </plugin>
      </plugins>
   </build>
</project>

Файл описания сборки pom.xml бандла bundle-publisher практически не отличается от pom.xml бандла bundle-exchange. Изменения касаются только наименования артифакта, наименования бандла и активатора. Здесь также в теге <scope> зависимостей указано значение provided, чтобы они не были включены в бандл. Бандл должен получить объект EventAdmin от фреймворка (см. тег Import-Package).

В результате сборки в поддиректории проекта target будет создан bundle в виде файла bundle-publisher-1.0.0.jar с манифестом следующего содержания :

Manifest-Version: 1.0
Bnd-LastModified: 1500974333681
Build-Jdk: 1.8.0_141
Built-By: EugeneMK
Bundle-Activator: com.osgi.bundle.PublisherActivator
Bundle-ManifestVersion: 2
Bundle-Name: Bundle publisher
Bundle-SymbolicName: com.osgi.bundle.bundle-publisher
Bundle-Version: 1.0.0
Created-By: Apache Maven Bundle Plugin
Export-Package: com.osgi.bundle;uses:="org.osgi.service.event,org.osgi.
 framework";version="1.0.0"
Import-Package: org.osgi.framework;version="[1.8,2)",org.osgi.service.
 event;version="[1.2,2)"
Tool: Bnd-1.50.0

Описание проекта framework-pubsub

Структура проекта framework-pubsub, представленная на следующем скриншоте, включает основной класс тестирования механизма publish/subscribe FrameworkService и набор бандлов в поддиректории bundles. Командный файл run.bat позволяет запустить готовый к тестированию пример без инсталляции проекта в Eclipse.

При описании OSGi приложения с использованием фреймворка Felix представлен механизм инсталляции фреймворка и перевода его в различные состояния, а также описан процесс инсталляции бандлов. Поэтому в этой статье остановимся только на тестировании механизма publish/subscribe, т.е. на последовательности инсталляции и запуска бандлов.

Листинг FrameworkService

В листинге класса FrameworkService на странице представлен только конструктор класса. Вспомогательные методы createFramework, installBundles, startBundles и printBundleParams с небольшими отличиями приведены на странице описания OSGi приложения. При желании Вы можете скачать исходные коды примера.

private Framework framework = null;

private String   cache =  "bundles-cache";
private String[] files = {
                    "bundles/org.apache.felix.eventadmin-1.4.8.jar",
                    "bundles/bundle-exchange-1.0.0.jar",
                    "bundles/bundle-publisher-1.0.0.jar" };
private String BUNDLE_PUBLISHER = "com.osgi.bundle.bundle-publisher";
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public FrameworkService() {
    createFramework();
    if (framework == null) {
        System.out.println ("framework is not created");
      	return;
    }
    try {
        try {
            installBundles();
            startBundles  ();
            //------------------------
            // создание объекта чтения из стандартного потока ввода
            Scanner in = new Scanner(System.in);
            // чтение строки из консоли
            System.out.print ("\n\nEnter pub : " );
            String cmd = in.nextLine();
            while (!cmd.equalsIgnoreCase("pub")) {
                cmd = in.nextLine();
            }
            //------------------------
            for (int i = 0; i < framework.getBundleContext()
                                       .getBundles().length; i++) {
                Bundle bundle = framework.getBundleContext()
                                         .getBundles()[i];
                if (bundle.getSymbolicName()
                          .equalsIgnoreCase(BUNDLE_PUBLISHER)) {
                    try {
                        bundle.start();
                        printBundleParams(bundle, "Bundle", false);
                    } catch (BundleException e) {
                    }
                }
            }
            //------------------------
            System.out.print ("\n\nEnter exit : " );
            // чтение строки из консоли
            while (!cmd.equalsIgnoreCase("exit"))
                cmd = in.nextLine();
            in.close();
            stopFramework();
        } catch (Exception e) {
            System.err.println("Exception : " + e.getMessage());
        }
    } finally { 
        if (framework != null) {
            try {
                framework.stop();
                framework.waitForStop(2000);
            } catch (BundleException e) {
            } catch (InterruptedException e) {}
        }
    } 
}

При тестировании механизма publish/subscribe приложение проходит три этапа:

  1. На первом этапе создается фреймворк и инсталлируются бандлы. После этого приложение стартует все бандлы за исключением bundle-publisher. Бандл bundle-exchange начинает обрабатывать события, связанные с изменением состояний бандлов, и ждет сообщения 'service/event/Currency'. Приложение останавливает работу, пользователь должен ввести в консоли "pub", чтобы перейти на следующий этап.
  2. На втором этапе после ввода пользователем "pub" bundle-publisher стартует и начинает отправлять сообщения. Бандл bundle-exchange обрабатывает сообщения об изменении состояния bundle-publisher и сообщения 'service/event/Currency'. Основное приложение приостанавливает процесс и ждет от пользователя ввода "exit" для перехода к третьему этапу.
  3. На третьем этапе после ввода пользователем "exit" приложение останавливает работу фреймворка и бандлов. Бандл bundle-exchange «успевает» обработать сообщение об остановке bundle-publisher.

Примечание :
приложение и бандлы выводят информацию в консоль параллельно в разных «потоках». Поэтому точки останова приложения "Enter pub : " и "Enter exit : " не совпадают с приведенными далее результатами тестирования.

Результаты тестирования

Результаты работы примера выводятся в консоль.

Старт фреймворка и инсталляция бандлов

На первом этапе приложение создает, инсталлирует и стартует фреймворк Felix. Если фреймворк стартовал, то приложение инсталлирует все бандлы. Информация о фреймворке и бандлах выводится в консоль. После инсталляции бандлов приложение стартует их за исключением bundle-publisher и переходит в режим ожидания ввода пользователем "pub".

Бандл bundle-exchange при старте сразу же подписывается на определенные события и успевает обработать сообщение об изменении состояния "самого себя". Т.е. задержка обработки изменений состояний во фреймворке превышает время регистрации обработчика события в бандле.


Framework is created
   Version = 5.6.2
   SymbolicName = org.apache.felix.framework
   Location = System Bundle
   BundleId = 0
   Framework state : INSTALLED
Framework state : STARTING
Framework state : ACTIVE

INSTALL BUNDLES ...

Bundle <org.apache.felix.eventadmin-1.4.8.jar>
   Version = 1.4.8
   SymbolicName = org.apache.felix.eventadmin
   Location = file:/D:/framework-pubsub/bundles/\
                       org.apache.felix.eventadmin-1.4.8.jar
   BundleId = 1
   Bundle state : INSTALLED
Bundle <bundle-exchange-1.0.0.jar>
   Version = 1.0.0
   SymbolicName = com.osgi.bundle.bundle-exchange
   Location = file:/D:/framework-pubsub/bundles/\
                       bundle-exchange-1.0.0.jar
   BundleId = 2
   Bundle state : INSTALLED
Bundle <bundle-publisher-1.0.0.jar>
   Version = 1.0.0
   SymbolicName = com.osgi.bundle.bundle-publisher
   Location = file:/D:/framework-pubsub/bundles/\
                       bundle-publisher-1.0.0.jar
   BundleId = 3
   Bundle state : INSTALLED

START BUNDLES

   Version = 1.4.8
   SymbolicName = org.apache.felix.eventadmin
   Location = file:/D:/framework-pubsub/bundles/\
                       org.apache.felix.eventadmin-1.4.8.jar
   BundleId = 1
   Bundle state : ACTIVE
   
   start : bundle-exchange
ExchangeActivator : \
        Service 'org/osgi/framework/BundleEvent/*' is registered
ExchangeActivator : \
        Service 'service/event/Currency' is registered
   Version = 1.0.0
   SymbolicName = com.osgi.bundle.bundle-exchange
   Location = file:/D:/framework-pubsub/bundles/\
                       bundle-exchange-1.0.0.jar
   BundleId = 2
   Bundle state : ACTIVE
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                          org/osgi/framework/BundleEvent/STARTED
    !    bundle.symbolicName = com.osgi.bundle.bundle-exchange
    !    bundle.id = 2
    !    event = org.osgi.framework.BundleEvent[source=\
    !                            com.osgi.bundle.bundle-exchange [2]]
    !    bundle = com.osgi.bundle.bundle-exchange [2]
    !    event.topics = org/osgi/framework/BundleEvent/STARTED
    -----------------------------------------------------------------

Enter pub : 
 

Старт бандла publish-subcribe

После ввода "pub" (сокращение от publisher), приложение стартует bundle-publisher, которое начинает отправлять сообщения. Бандл bundle-exchange обрабатывает сообщения 'service/event/Currency' и 'org/osgi/framework/BundleEvent/*'.

Обратите внимание, что сначала bundle-exchange успевает получить сообщение 'service/event/Currency', после которого два сообщения об изменении состояния bundle-publisher, хотя теоретически должно было быть наоборот. Задержки связаны с дополнительной обработкой событий в фреймворке и определением подписавшегося на эти события обработчиков.


Enter pub : pub
  START : bundle-publisher
   Bundle state : ACTIVE

bundle-publisher : 
    send event 'service/event/Currency', currency = rub
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                                          service/event/Currency
    !   currency = rub
    !   event.topics = service/event/Currency
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                         org/osgi/framework/BundleEvent/RESOLVED
    !   bundle.symbolicName = com.osgi.bundle.bundle-publisher
    !   bundle.id = 3
    !   event = org.osgi.framework.BundleEvent[source=\
    !                           com.osgi.bundle.bundle-publisher [3]]
    !   bundle = com.osgi.bundle.bundle-publisher [3]
    !   event.topics = org/osgi/framework/BundleEvent/RESOLVED
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                          org/osgi/framework/BundleEvent/STARTED
    !   bundle.symbolicName = com.osgi.bundle.bundle-publisher
    !   bundle.id = 3
    !   event = org.osgi.framework.BundleEvent[source=\
    !                           com.osgi.bundle.bundle-publisher [3]]
    !   bundle = com.osgi.bundle.bundle-publisher [3]
    !   event.topics = org/osgi/framework/BundleEvent/STARTED
    -----------------------------------------------------------------
bundle-publisher : 
    send event 'service/event/Currency', currency = usd
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                                          service/event/Currency
    !   currency = usd
    !   event.topics = service/event/Currency
    -----------------------------------------------------------------
bundle-publisher : 
    send event 'service/event/Currency', currency = eur
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                                          service/event/Currency
    !   currency = eur
    !   event.topics = service/event/Currency
    -----------------------------------------------------------------
 

Останов бандлов и завершение работы

При остановке приложения фреймворк останавливает бандлы, после чего завершает работу сам. Здесь, следует отметить, что bundle-exchange успевает обработать сообщение об остановке bundle-publisher, но после этого завершает работу сам, и фреймворк выводит предупреждающее сообщение, что обработка событий EventAdmin прервана и не может быть должным образом обработана. Т.е. необходимо было во время завершить работу сервиса, связанную с контролем состояний бандлов.


Enter exit : exit

stop : bundle-publisher
    -----------------------------------------------------------------
    ! ExchangeSubscriber : received event on topic = \
    !                          org/osgi/framework/BundleEvent/STOPPED
    !   bundle.symbolicName = com.osgi.bundle.bundle-publisher
    !   bundle.id = 3
    !   event = org.osgi.framework.BundleEvent[source=\
    !                           com.osgi.bundle.bundle-publisher [3]]
    !   bundle = com.osgi.bundle.bundle-publisher [3]
    !   event.topics = org/osgi/framework/BundleEvent/STOPPED
    -----------------------------------------------------------------
WARNING: EventAdmin: Event Task Processing Interrupted. \
                           Events may not be recieved in proper order.
stop : bundle-exchange
stop : framework
 

Скачать пример

Исходный код рассмотренного примера использования механизма publish/subscribe можно скачать здесь (1.16 Мб).

Пример создания фреймворка Felix, перевода его в различные состояния и инсталляции бандлов в OSGi контейнер представлен здесь.

  Рейтинг@Mail.ru