Перейти к основному содержимому

Введение в OSGi

· 8 мин. чтения

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.foreach.osgi.sample.activator.HelloWorld
</Bundle-Activator>
<Private-Package>
com.foreach.osgi.sample.activator
</Private-Package>
</instructions>
</configuration>
</plugin>

В разделе инструкций мы указываем значения заголовков OSGi , которые хотим включить в файл МАНИФЕСТА пакета.

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

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

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

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

<KARAF_HOME>/bin/karaf start

где <KARAF_HOME> — это папка, в которой установлен Karaf . Когда появится приглашение консоли Karaf , мы можем выполнить следующую команду для установки пакета:

> bundle:install mvn:com.foreach/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

В консоли появляется «До свидания, мир» в соответствии с кодом в методе stop() .

7. Служба OSGi

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

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

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

package com.foreach.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.foreach.osgi.sample.service.implementation.GreeterImpl
</Bundle-Activator>
<Private-Package>
com.foreach.osgi.sample.service.implementation
</Private-Package>
<Export-Package>
com.foreach.osgi.sample.service.definition
</Export-Package>
</instructions>
</configuration>
</plugin>

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

Благодаря этому OSGi позволит другим пакетам вызывать только методы, указанные в интерфейсе службы. Пакет com.foreach.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.foreach</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.foreach/osgi-intro-sample-service/1.0-SNAPSHOT
Bundle ID: 64
> install mvn:com.foreach/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 может быть жизнеспособным решением.

Код для этого поста можно найти на GitHub .