OSGiの紹介

1前書き

いくつかのJavaのミッションクリティカルなミドルウェアアプリケーションには、厳しい技術的要件があります。

実行中のサービスを中断しないようにホットデプロイをサポートしなければならないものもあれば、外部のレガシーシステムをサポートするために同じパッケージの異なるバージョンで作業できる必要があるものもあります。

OSGiプラットフォームは、この種の要件をサポートするための実行可能なソリューションです。

  • 「Open Service Gateway Initiative」は、Javaベースのコンポーネントシステムを定義する仕様です。

それ以来、これはコンポーネントシステムの優れた標準となり、今日では広く使用されています。たとえば、 Eclipse IDE は、 OSGi ベースのアプリケーションです。

この記事では、 Apache が提供する実装を利用した OSGi の基本機能をいくつか探ります。

2 OSGiの基本

OSGiでは、単一のコンポーネントをバンドルと呼びます。

論理的には、 バンドルは独立したライフサイクルを持つ機能の一部です - これは独立して起動、停止、削除できることを意味します。

技術的には、バンドルはOSGi固有のヘッダーをいくつか含む MANIFEST.MF ファイルを含む単なるjarファイルです。

OSGi プラットフォームは、バンドルが利用可能になったとき、またはバンドルがプラットフォームから削除されたときについての通知を受け取る方法を提供します。

これにより、適切に設計されたクライアントは、依存しているサービスが瞬間的に利用できなくても、おそらく機能が低下しても機能し続けることができます。

そのため、バンドルは、どのパッケージにアクセスする必要があるかを明示的に宣言する必要があり、 OSGi プラットフォームは、依存関係がバンドル自体またはプラットフォームにすでにインストールされている他のバンドルで使用可能な場合にのみ開始します。

3ツールを入手する

私たちは OSGi で私たちの旅を始めることができます。http://www.apache.org/dyn/closer.lua/karaf/4.1.3/apache-karaf-4.1.3.zip[この記事から Apache Karaf の最新版をダウンロードしてください。リンク]。 Apache Karaf は、 __ OSGiベースのアプリケーションを実行するプラットフォームです。これは、 Apache Felix と呼ばれる OSGi 仕様の Apache による実装に基づいています。

Karaf Felix の上にいくつかの便利な機能を提供しています。それは OSGi に精通するのを手助けするでしょう、例えば私達がプラットフォームと対話することを可能にするコマンドラインインターフェース。

Karaf をインストールするには、https://karaf.apache.org/manual/latest/# quick start[公式のドキュメント]からインストール手順に従ってください。

4.バンドルエントリポイント

OSGi環境でアプリケーションを実行するには、それを OSGi バンドルとしてパックし、アプリケーションのエントリポイントを定義する必要があります。これは通常の public static void main(String[]args) メソッドではありません。

それでは、まず__OSGiベースの「Hello World」アプリケーションを作成しましょう。

コアOSGi APIへの単純な依存関係の設定を始めます。

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

依存関係は OSGi ランタイムで使用可能になるため、依存関係は provided として宣言されており、バンドルで埋め込む必要はありません。

それでは、単純な 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() はバンドルが停止する直前に呼び出されます。

各バンドルには、最大1つの BundleActivator を含めることができます。両方のメソッドに提供される BundleContext オブジェクトは、 OSGi ランタイムと対話することを可能にします。私たちはすぐにそれに戻ります。

5バンドルを構築する

pom.xml を変更して、実際のOSGiバンドルにしましょう。

まず第一に、私たちはjarではなくバンドルを構築するつもりであることを明示的に述べなければなりません。

<packaging>bundle</packaging>

次に、 HelloWorld クラスを OSGi バンドルとしてパッケージ化するために、__maven-bundle-pluginを利用します。

<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>

説明セクションでは、バンドルのMANIFESTファイルに含める 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.baeldung/osgi-intro-sample-activator/1.0-SNAPSHOT
Bundle ID: 63

assignedTheバンドルがインストールされました。次のコマンドで起動できます

> bundle:start 63
Hello World

バンドルが開始されるとすぐに「Hello World」がすぐに表示されます。これでバンドルを停止してアンインストールすることができます。

> bundle:stop 63
> bundle:uninstall 63

stop() メソッドのコードに従って、「Goodbye World」がコンソールに表示されます。

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();
    }
}

OSGi プラットフォームにサービスの新しいインスタンスを登録するように要求する手段として、 BundleContext を使用します。

私達はまた私達の簡単なシナリオでは必要とされないサービスの種類と可能な設定パラメータのマップを提供するべきです。

それでは、 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 {
}

__BundleActivatorのstart()メソッドを実装しましょう。

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

各バンドルに割り当てられているID番号は異なる可能性があることに常に留意してください。

それでは、クライアントバンドルを起動しましょう。

> start 65

したがって、クライアントがアクティブで、サービスを待っているので、何も起こりません。

> start 64
Registering service.
Service registered.
Hello John

サービスのBundleActivatorが起動するとすぐに、サービスがプラットフォームに登録されます。それは順番に、それが待っていたサービスが利用可能であることをクライアントに通知します。

その後、クライアントはサービスへの参照を取得し、それを使用してサービスバンドルを通じて配信された実装を呼び出します。

10結論

この記事では、OSGiの潜在的な機能を理解するのに十分であるという直接的な例を用いて、OSGiの本質的な機能を調べました。

結論として、単一のアプリケーションが何の問題もなく更新されなければならないということを保証しなければならないときはいつでも、OSGiは実行可能な解決策になり得る。

この記事のコードはhttps://github.com/eugenp/tutorials/tree/master/osgi[over on GitHub]にあります。