«1. Введение

Несколько критически важных Java-приложений и промежуточного программного обеспечения имеют некоторые жесткие технологические требования.

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

Платформы OSGi представляют собой жизнеспособное решение для поддержки таких требований.

Инициатива Open Service Gateway — это спецификация, определяющая компонентную систему на основе Java. В настоящее время им управляет OSGi Alliance, а его первая версия датируется 1999 годом.

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

В этой статье мы рассмотрим некоторые основные функции OSGi, использующие реализацию, предоставляемую Apache.

2. Основы OSGi

В OSGi один компонент называется комплектом.

Логически, пакет — это часть функциональности с независимым жизненным циклом, то есть его можно запускать, останавливать и удалять независимо.

Технически пакет — это просто jar-файл с файлом MANIFEST.MF, содержащим некоторые специфичные для OSGi заголовки.

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

Из-за этого пакет должен явно объявлять, к каким пакетам он должен иметь доступ, и платформа OSGi запустит его только в том случае, если зависимости доступны в самом пакете или в других пакетах, уже установленных на платформе.

3. Получение инструментов

Мы начнем наше путешествие в OSGi, загрузив последнюю версию Apache Karaf по этой ссылке. Apache Karaf — это платформа, которая запускает приложения на базе OSGi; он основан на реализации Apache спецификации OSGi под названием Apache Felix.

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

Чтобы установить Karaf, следуйте инструкциям по установке из официальной документации.

4. Точка входа пакета

Чтобы выполнить приложение в среде OSGi, мы должны упаковать его как пакет OSGi и определить точку входа приложения, а это не обычный public static void main(String[] args ) метод.

Итак, давайте начнем с создания приложения «Hello World» на основе OSGi.

Начнем с настройки простой зависимости от основного OSGi API:

<dependency>
    <groupId>org.osgi</groupId> 
    <artifactId>org.osgi.core</artifactId>
    <version>6.0.0</version>
    <scope>provided</scope>
</dependency>

Зависимость объявляется предоставленной, потому что она будет доступна в среде выполнения OSGi, и ее не нужно встраивать в пакет.

Давайте теперь напишем простой класс HelloWorld:

public class HelloWorld implements BundleActivator {
    public void start(BundleContext ctx) {
        System.out.println("Hello world.");
    }
    public void stop(BundleContext bundleContext) {
        System.out.println("Goodbye world.");
    }
}

BundleActivator — это интерфейс, предоставляемый OSGi, который должен быть реализован классами, которые являются точками входа для пакета.

Метод start() вызывается платформой OSGi при запуске пакета, содержащего этот класс. С другой стороны, функция stop() вызывается непосредственно перед остановкой пакета.

Имейте в виду, что каждый пакет может содержать не более одного BundleActivator. Объект BundleContext, предоставляемый обоим методам, позволяет взаимодействовать со средой выполнения OSGi. Мы скоро вернемся к этому.

5. Сборка пакета

Давайте изменим файл pom.xml и сделаем его настоящим пакетом OSGi.

Прежде всего, мы должны явно указать, что собираемся создавать пакет, а не банку:

<packaging>bundle</packaging>

Затем мы используем плагин maven-bundle-plugin, любезно предоставленный сообществом Apache Felix, для упакуйте класс HelloWorld как пакет OSGi:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>3.3.0</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-SymbolicName>
                ${pom.groupId}.${pom.artifactId}
            </Bundle-SymbolicName>
            <Bundle-Name>${pom.name}</Bundle-Name>
            <Bundle-Version>${pom.version}</Bundle-Version>
            <Bundle-Activator>
                com.baeldung.osgi.sample.activator.HelloWorld
            </Bundle-Activator>
            <Private-Package>
                com.baeldung.osgi.sample.activator
            </Private-Package>            
        </instructions>
    </configuration>
</plugin>

В разделе инструкций мы указываем значения заголовков OSGi, которые мы хотим включить в файл MANIFEST пакета.

«Bundle-Activator — это полное имя реализации BundleActivator, которое будет использоваться для запуска и остановки пакета, и оно относится к классу, который мы только что написали.

Private-Package не является заголовком OSGi, но он используется, чтобы указать плагину включить пакет в пакет, но не делать его доступным для других. Теперь мы можем собрать пакет с помощью обычной команды mvn clean install.

6. Установка и запуск пакета

Запустим Karaf, выполнив команду:

<KARAF_HOME>/bin/karaf start

где \u003cKARAF_HOME\u003e — папка, в которую установлен Karaf. Когда появится приглашение консоли Karaf, мы можем выполнить следующую команду для установки пакета:

> bundle:install mvn:com.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT
Bundle ID: 63

Это указывает Karaf загрузить пакет из локального репозитория Maven.

В ответ Karaf распечатывает числовой идентификатор, назначенный пакету, который зависит от количества уже установленных пакетов и может варьироваться. Пакет только что установлен, теперь мы можем запустить его с помощью следующей команды:

> bundle:start 63
Hello World

«Hello World» появляется сразу же после запуска пакета. Теперь мы можем остановить и удалить пакет с помощью:

> bundle:stop 63
> bundle:uninstall 63

На консоли появится надпись «Goodbye World» в соответствии с кодом в методе stop().

7. Служба OSGi

Давайте продолжим писать простую службу OSGi, интерфейс, который предоставляет метод для приветствия людей:

package com.baeldung.osgi.sample.service.definition;
public interface Greeter {
    public String sayHiTo(String name);
}

Давайте напишем ее реализацию, которая также является BundleActivator, так что мы сможет создать экземпляр службы и зарегистрировать ее на платформе при запуске пакета:

package com.baeldung.osgi.sample.service.implementation;
public class GreeterImpl implements Greeter, BundleActivator {

    private ServiceReference<Greeter> reference;
    private ServiceRegistration<Greeter> registration;

    @Override
    public String sayHiTo(String name) {
        return "Hello " + name;
    }

    @Override 
    public void start(BundleContext context) throws Exception {
        System.out.println("Registering service.");
        registration = context.registerService(
          Greeter.class, 
          new GreeterImpl(), 
          new Hashtable<String, String>());
        reference = registration
          .getReference();
    }

    @Override 
    public void stop(BundleContext context) throws Exception {
        System.out.println("Unregistering service.");
        registration.unregister();
    }
}

Мы используем BundleContext как средство запроса платформы OSGi на регистрацию нового экземпляра службы.

Мы также должны указать тип службы и карту возможных параметров конфигурации, которые не нужны в нашем простом сценарии. Теперь приступим к настройке maven-bundle-plugin:

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-SymbolicName>
                ${project.groupId}.${project.artifactId}
            </Bundle-SymbolicName>
            <Bundle-Name>
                ${project.artifactId}
            </Bundle-Name>
            <Bundle-Version>
                ${project.version}
            </Bundle-Version>
            <Bundle-Activator>
                com.baeldung.osgi.sample.service.implementation.GreeterImpl
            </Bundle-Activator>
            <Private-Package>
                com.baeldung.osgi.sample.service.implementation
            </Private-Package>
            <Export-Package>
                com.baeldung.osgi.sample.service.definition
            </Export-Package>
        </instructions>
    </configuration>
</plugin>

Стоит отметить, что на этот раз через заголовок Export-Package был экспортирован только пакет com.baeldung.osgi.sample.service.definition. .

Благодаря этому OSGi позволит другим пакетам вызывать только методы, указанные в интерфейсе службы. Пакет com.baeldung.osgi.sample.service.implementation помечен как закрытый, поэтому никакой другой пакет не сможет получить прямой доступ к членам реализации.

8. Клиент OSGi

Давайте теперь напишем клиент. Он просто ищет службу при запуске и вызывает ее:

public class Client implements BundleActivator, ServiceListener {
}

Давайте реализуем метод start() BundleActivator:

private BundleContext ctx;
private ServiceReference serviceReference;

public void start(BundleContext ctx) {
    this.ctx = ctx;
    try {
        ctx.addServiceListener(
          this, "(objectclass=" + Greeter.class.getName() + ")");
    } catch (InvalidSyntaxException ise) {
        ise.printStackTrace();
    }
}

Метод addServiceListener() позволяет клиенту запрашивать у платформы отправку уведомлений о службы, которая соответствует предоставленному выражению.

Выражение использует синтаксис, аналогичный LDAP, и в нашем случае мы запрашиваем уведомления о сервисе Greeter.

Перейдем к методу обратного вызова:

public void serviceChanged(ServiceEvent serviceEvent) {
    int type = serviceEvent.getType();
    switch (type){
        case(ServiceEvent.REGISTERED):
            System.out.println("Notification of service registered.");
            serviceReference = serviceEvent
              .getServiceReference();
            Greeter service = (Greeter)(ctx.getService(serviceReference));
            System.out.println( service.sayHiTo("John") );
            break;
        case(ServiceEvent.UNREGISTERING):
            System.out.println("Notification of service unregistered.");
            ctx.ungetService(serviceEvent.getServiceReference());
            break;
        default:
            break;
    }
}

Когда происходит какая-либо модификация, связанная с сервисом Greeter, метод уведомляется.

Когда служба регистрируется на платформе, мы получаем ссылку на нее, сохраняем ее локально, а затем используем для получения объекта службы и его вызова.

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

Теперь нам просто нужно написать метод stop():

public void stop(BundleContext bundleContext) {
    if(serviceReference != null) {
        ctx.ungetService(serviceReference);
    }
}

Здесь снова мы отключаем службу, чтобы покрыть случай, когда клиент останавливается до остановки службы. Давайте окончательно взглянем на зависимости в pom.xml:

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>osgi-intro-sample-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.osgi</groupId>
    <artifactId>org.osgi.core</artifactId>
    <version>6.0.0</version>
</dependency>

9. Клиент и служба

Давайте теперь установим пакеты клиента и службы в Karaf, выполнив:

> install mvn:com.baeldung/osgi-intro-sample-service/1.0-SNAPSHOT
Bundle ID: 64
> install mvn:com.baeldung/osgi-intro-sample-client/1.0-SNAPSHOT
Bundle ID: 65

Всегда сохраняйте Имейте в виду, что идентификационные номера, присвоенные каждому пакету, могут различаться.

Давайте теперь запустим клиентский пакет:

> start 65

Поэтому ничего не происходит, потому что клиент активен и ожидает службы, с которой мы можем начать:

> start 64
Registering service.
Service registered.
Hello John

«

«Что происходит, так это то, что как только BundleActivator службы запускается, служба регистрируется на платформе. Это, в свою очередь, уведомляет клиента о том, что служба, которую он ждал, доступна.

Затем клиент получает ссылку на службу и использует ее для вызова реализации, поставляемой через пакет службы.

10. Заключение

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

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