Пример OSGi сервиса сообщений

Рассмотрим пример OSGi сервиса печати сообщений в консоль. Для этого будем использовать два бандла. Один бандл зарегистрируется в контейнере OSGi в качестве сервиса. Второй bundle будет вызывать сервис для «вывода» сообщений в консоль :

  • service-printer выводит в консоль сообщения;
  • service-client периодически в потоке (Thread) отправляет сообщения.

Описание и технологию создания bundle не рассматриваем. Желающие могут познакомиться как со спецификацией OSGi, так и со структурой бандлов здесь. Терминология «bundle» заимствована из спецификации OSGi.

На следующем рисунке представлена схема возможных состояний OSGI bundle. Если с бандлом нет проблем, то после инсталляции (Install) он переходит в состяние Resolved. После этого бандл может быть стартован (start) и остановлен (stop). Проверим это при тестировании бандлов.

Проблемы у бандла могут возникнуть, если в OSGi контейнере/фреймворке отсутствуют необходимые для его функционирования пакеты (package). В этом случае бандл остается в состоянии «Installed», и все попытки стартовать такой bundle сопровождаются вызовом исключений (Exception).

Для проверки функционирования наших бандлов будет использоваться OSGi контейнер Equinox от Eclipse Foundation. Данный фреймворк позволяет проверить наличие необходимых для функционирования бандлов пакетов.

Структуры проектов создания OSGi бандлов

На следующем скриншоте представлена структура проектов OSGi. Для разработки OSGi бандлов используется фреймворк Maven.

Bundle сервис service-printer включает :

  • интерфейс IPrinter.java
  • имплементацию PrinterImpl.java
  • активатор бандла PrinterActivator.java

Bundle клиент service-client включает :

  • класс отправления сообщений RunnablePrinter.java
  • активатор бандла ClientActivator.java

OSGi сервис service-printer

Листинг интерфейса IPrinter.java

Интерфейс IPrinter.java включает только один метод print, которому в качестве параметра необходимо передать текст сообщения.

package osgi.service.printer;

public interface IPrinter 
{
    public void print(String message);
}

Листинг имплементации PrinterImpl.java

Имплементация интерфейса PrinterImpl.java реализует метод print, в котором выводит текст сообщения в консоль. При выводе сообщения используется класс форматирования даты DateFormat пакета java.text.

package osgi.service.printer;

import java.util.Date;
import java.text.DateFormat;

public class PrinterImpl implements IPrinter 
{
    public void print(String message)
    {
        DateFormat dtf = DateFormat.getTimeInstance(DateFormat.LONG);
        String time = dtf.format(new Date());
        System.out.println(time + " : " + message);
    }
}

Листинг активатора бандла PrinterActivator.java

Активатор бандла реализует интерфейс BundleActivator, включающий методы start и stop. В методе start в контексте фреймворка регистрируется сервис serviceRegistration. В методе stop регистрация сервиса из фреймворка удаляется (unregister).

package osgi.service.printer;

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

public class PrinterActivator implements BundleActivator
{
    private ServiceRegistration<IPrinter> serviceRegistration;

    //---------------------------------------------------------------
    @Override
    public void start(BundleContext context) throws Exception
    {
        serviceRegistration = (ServiceRegistration<IPrinter>)
                 context.registerService (IPrinter.class.getName(),  
                 new PrinterImpl(), null);
        System.out.println("start : service-printer");
    }
    //---------------------------------------------------------------
    @Override
    public void stop(BundleContext context) throws Exception
    {
        serviceRegistration.unregister();
        System.out.println("stop : service-printer");
    }
    //---------------------------------------------------------------
}

Листинг pom.xml

Файл описания сборки бандла pom.xml включает GAV параметры и полное наименование артифакта. В качестве зависимости используется org.osgi.core.

<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>osgi.service.printer</groupId>
  <artifactId>service-printer</artifactId>
  <packaging>bundle</packaging>
  <version>1.0.0</version>

  <name>Service printer</name>
  <url>http://maven.apache.org</url>

  <properties>
      <project.build.sourceEncoding>
         UTF-8
      </project.build.sourceEncoding>
  </properties>
  
  <dependencies>
     <dependency>
          <groupId>org.osgi</groupId>
          <artifactId>org.osgi.core</artifactId>
          <version>6.0.0</version>
      </dependency>
  </dependencies>
  
   <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>2.3.2</version>
          </plugin>
          <plugin>
              <groupId>org.apache.felix</groupId>
              <artifactId>maven-bundle-plugin</artifactId>
              <version>2.3.7</version>
              <extensions>true</extensions>
              <configuration>
                  <instructions>
                     <Bundle-SymbolicName>
                        .service-hello
                     </Bundle-SymbolicName>
                     <Bundle-Name></Bundle-Name>
                     <Bundle-Version>
                        
                     </Bundle-Version>
                     <Bundle-Activator>
                        osgi.service.printer.PrinterActivator
                     </Bundle-Activator>
                     <Import-Package>
                             org.osgi.framework;version="1.6.0"
                      </Import-Package>
                  </instructions>
              </configuration>
          </plugin>
      </plugins>
   </build>
</project>

Особый интерес представляет секция Import-Package, в которой указывается зависимость от фреймворка org.osgi.framework версии "1.6.0", который будет использован для тестирования.

Тестирование сервиса service-printer

Для тестирования бандлов используется фреймворк Equinox (org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar) от Eclipse Foundation, который входит в архив примеров и размещается в отдельной поддиректории osgi.container. Команды фреймворка Equinox для просмотра и управления состояниями бандлов представлены здесь.

В файле конфигурации configuration/config.ini определяется список бандлов. Внесем в него следующую строку :

osgi.bundles=bundles/service-printer-1.0.0.jar@start

В результате фреймворк инсталлирует и сразу же стартует бандл «service-printer». Информацию фреймворк выводит в консоль в следующем виде :


D:\osgi.container>java -jar org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console

osgi> start : service-printer

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1       ACTIVE      osgi.service.printer.service-hello_1.0.0

osgi> services (objectClass=*Printer)
{osgi.service.printer.IPrinter}={service.id=29}
  Registered by bundle: osgi.service.printer.service-hello_1.0.0 [1]
  No bundles using service.

osgi> stop 1
stop : service-printer

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1       RESOLVED    osgi.service.printer.service-hello_1.0.0
 

По команде ss выводим список бандлов, включающий их идентификаторы и состояния. Командой services с соответствующей маской (objectClass=*Printer) выводим информацию о сервисе. После этого останавливаем бандл service-printer и просматриваем состояния бандлов.

Пакеты фреймворка

Для просмотра используемых фрейморком пакетов необходимо выполнить команду packages, в результате чего в консоль будет выведен список всех пакетов. Обратите внимание, что после фреймворка org.osgi.framework (с версией "1.6.0") выводится используемый его бандл «service-hello» с атрибутом imports.


osgi> packages
...
org.osgi.framework; version="1.6.0"<org.eclipse.osgi_3.7.1.R37x_v20110808-1106 [0]>
        osgi.service.printer.service-hello_1.0.0 [1] imports
...
osgi.service.printer; version="1.0.0"<osgi.service.printer.service-hello_1.0.0 [1]>
 

OSGi bundle service-client

Листинг класса RunnablePrinter.java

Класс RunnablePrinter.java реализует интерфейс Runnable, переопределяя методы start, stop и run. Метод setPrinterService в качестве параметра получает ссылку на сервис IPrinter. В методе run каждые 3 секунды вызывается метод print сервиса. Количество вызовов не превышает 5, после чего отправка сообщений прекращается.

package osgi.service.client;

import osgi.service.printer.IPrinter;

public class RunnablePrinter implements Runnable 
{
    private final   int TWO_SECS = 3000;
    private static  int counter  = 0; 
    private boolean stop;

    private IPrinter service;

    void setPrinterService(IPrinter service)
    {
        this.service = service;
    }

    void start()
    {
        stop = false;
        new Thread(this).start();
    }
    void stop()
    {
        stop = true;
    }
    public void run() {
        while (!stop) {
            service.print("hello [" + 
                   String.valueOf(++counter) + "] ...");
            try {
                Thread.sleep(TWO_SECS);
                if (counter == 5) {
                    counter = 0;
                    stop = true;
                }
            } catch (InterruptedException e) {
                stop = true;
            }
        }
    }
}

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

Активатор клиента ClientActivator.java реализует два интерфейса (BundleActivator, ServiceListener) переопределяя методы start, stop и serviceChanged. В первых двух методах выполняется подключение к сервису при старте бандла и отключение от сервиса при его останове. Метод serviceChanged фреймворк вызывает, когда регистрируется или останавливается соответствующий сервис.

Если по каким-либо причинам не удается получить ссылку на сервис при старте, т.е. если сервис еще не зарегистрирован во фреймворке, то подключение будет выполнено сразу же после его регистрации уже при стартованном service-client. За это отвечает метод serviceChanged интерфейса ServiceListener. Сервис также может быть остановлен прежде чем будет остановлен бандл service-client. Тогда фреймворк также вызовет метод serviceChanged с соответствующим параметром ServiceEvent : serviceEvent.getType() == ServiceEvent.UNREGISTERING.

package osgi.service.client;

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

import osgi.service.printer.IPrinter;

public class ClientActivator implements BundleActivator, ServiceListener
{
    private RunnablePrinter             runnablePrinter  = null;
    private BundleContext               bundleContext    = null;
    private ServiceReference<IPrinter>  serviceReference = null;
    //---------------------------------------------------------------
    @Override
    public void start(BundleContext context) throws Exception
    {
        runnablePrinter = new RunnablePrinter();

        this.bundleContext = context;
        serviceReference = (ServiceReference<IPrinter>) 
                context.getServiceReference(IPrinter.class.getName());
        if (serviceReference != null) {
            IPrinter printer = (IPrinter) 
                                context.getService(serviceReference);
            if (printer != null) {
                runnablePrinter.setPrinterService(printer);
                runnablePrinter.start();
            }
        }
        context.addServiceListener(this, "(objectClass=" + 
                                     IPrinter.class.getName() + ")");
        System.out.println("start : service-client");
    }
    //---------------------------------------------------------------
    @Override
    public void stop(BundleContext context) throws Exception
    {
        if (serviceReference != null)
            context.ungetService(serviceReference);

        context.removeServiceListener(this);
        System.out.println("stop : service-client");
    }
    //---------------------------------------------------------------
    @Override
    public void serviceChanged(ServiceEvent serviceEvent) 
    {
        switch (serviceEvent.getType()) {
            case ServiceEvent.UNREGISTERING :
                runnablePrinter.stop();
                bundleContext.ungetService(
                                serviceEvent.getServiceReference());
                break;
            case ServiceEvent.REGISTERED :
                IPrinter printer = (IPrinter) bundleContext.getService(
                                    serviceEvent.getServiceReference());
                if (printer != null) {
                    runnablePrinter.setPrinterService(printer);
                    runnablePrinter.start();
                }
                break;
        }
    }
    //---------------------------------------------------------------
}

Листинг pom.xml

Листинг pom.xml клиента ни чем, кроме параметров GAV, не отличается от соответствующего файла сервиса, и его содержимое не приводится, чтобы не загромождать страницу дублируемой информацией. Желающие могут скачать рассматриваемые примеры, включающие также и файлы pom.xml.

Тестирование сервиса взаимодействия бандлов

Внесем в файл конфигурации configuration/config.ini следующие строки :

osgi.bundles=bundles/service-printer-1.0.0.jar@start, \
osgi.bundles=bundles/service-client-1.0.0.jar@start

Теперь фреймворк стартует оба бандла, и service-client сразу же начинает отправлять сообщения. Если просмотреть сервис IPrinter, то увидим, что он используется бандлом service-client.

На следующем шаге останавливаем сервис и перестартуем клиента. Теперь клиент не может отправить сообщения и находится в режиме ожидания. Как только сервис стартует, отправка сообщений возобновится.


D:\osgi.container>java -jar org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar -console
		   
osgi>
start : service-printer
start : service-client

14:26:24 MSK : hello [1] ...
14:26:27 MSK : hello [2] ...
14:26:30 MSK : hello [3] ...
14:26:33 MSK : hello [4] ...
14:26:37 MSK : hello [5] ...

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1       ACTIVE      osgi.service.printer.service-hello_1.0.0
2       ACTIVE      osgi.service.client.service-client_1.0.0

osgi> services (objectClass=*IPrinter)
{osgi.service.printer.IPrinter}={service.id=32}
  Registered by bundle: osgi.service.printer.service-hello_1.0.0 [1]
  Bundles using service:
    osgi.service.client.service-client_1.0.0 [2]

osgi> stop 1
stop : service-printer

osgi> stop 2
stop : service-client

osgi> start 2
start : service-client

osgi> start 1
start : service-printer

osgi>
14:27:34 MSK : hello [1] ...
14:27:37 MSK : hello [2] ...
14:27:40 MSK : hello [3] ...
14:27:43 MSK : hello [4] ...
14:27:46 MSK : hello [5] ...

osgi> stop 1
stop : service-printer

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.7.1.R37x_v20110808-1106
1       RESOLVED    osgi.service.printer.service-hello_1.0.0
2       ACTIVE      osgi.service.client.service-client_1.0.0
 

После получения всех сообщений останавливаем сервис и проверяем его состояние. Оно должно быть «RESOLVED».

Пакеты фреймворка

При просмотре пакетов обратите внимание на использование фреймворка org.osgi.framework бандлами service-hello и service-client, включающими атрибут imports, а также использование сервиса service-hello бандлом service-client.


osgi> packages
...
org.osgi.framework; version="1.6.0"<org.eclipse.osgi_3.7.1.R37x_v20110808-1106 [0]>
        osgi.service.printer.service-hello_1.0.0 [1] imports
        osgi.service.client.service-client_1.0.0 [2] imports
...
osgi.service.printer; version="1.0.0"<osgi.service.printer.service-hello_1.0.0 [1]>
        osgi.service.client.service-client_1.0.0 [2] imports
osgi.service.client; version="1.0.0"<osgi.service.client.service-client_1.0.0 [2]>
 

Скачать исходный код

Исходные коды рассмотренных примеров в виде проектов Eclipse можно скачать здесь (1.22 Мб). Архив проекта дополнительно содержит директорию osgi.container, включающую фреймворк org.eclipse.osgi_3.7.1.R37x_v20110808-1106.jar для создания OSGi контейнера Equinox и тестирования бандлов.

  Рейтинг@Mail.ru