1. 序章

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

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

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

Open Service Gateway Initiativeは、Javaベースのコンポーネントシステムを定義する仕様です。現在、 OSGi Alliance によって管理されており、最初のバージョンは1999年にさかのぼります。

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

この記事では、Apacheによって提供される実装を活用したOSGiのいくつかの基本機能について説明します。

2. OSGiの基本

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

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

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

OSGi プラットフォームは、バンドルが利用可能になったとき、またはバンドルがプラットフォームから削除されたときに通知を受け取る方法を提供します。 これにより、適切に設計されたクライアントは、依存しているサービスが一時的に利用できなくなった場合でも、機能が低下した状態で動作し続けることができます。

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

3. ツールの入手

このリンクからApacheKaraf の最新バージョンをダウンロードすることにより、OSGiでの旅を開始します。 Apache Karaf は、OSGi-ベースのアプリケーションを実行するプラットフォームです。 これは、ApacheApacheFelixと呼ばれるOSGi仕様の実装に基づいています。

Karaf は、 Felix に加えて、 OSGi を理解するのに役立ついくつかの便利な機能を提供します。たとえば、対話を可能にするコマンドラインインターフェイスです。プラットフォームで。

Karaf をインストールするには、公式ドキュメントのインストール手順に従ってください。

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

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

それでは、OSGi-ベースの「HelloWorld」アプリケーションを構築することから始めましょう。

コアOSGiAPIへの単純な依存関係の設定を開始します。

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

依存関係はprovidedとして宣言されます。これは、 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()は、バンドルが停止する直前に呼び出されます。

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

5. バンドルの構築

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

まず、jarではなくバンドルを作成することを明示的に指定する必要があります。

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

手順のセクションでは、バンドルのMANIFESTファイルに含めるOSGiヘッダーの値を指定します。

Bundle-Activator は、バンドルの開始と停止に使用される BundleActivator 実装の完全修飾名であり、今作成したクラスを指します。

Private-Package はOSGiヘッダーではありませんが、プラグインにパッケージをバンドルに含めるように指示するために使用されますが、他のパッケージでは使用できません。 これで、通常のコマンド mvn cleaninstallを使用してバンドルをビルドできます。

6. バンドルのインストールと実行

次のコマンドを実行して、Karafを起動しましょう。

<KARAF_HOME>/bin/karaf start

どこはフォルダですカラフがインストールされています。 Karaf コンソールのプロンプトが表示されたら、次のコマンドを実行してバンドルをインストールできます。

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

これは、ローカルのMavenリポジトリからバンドルをロードするようにKarafに指示します。

その見返りに、カラフは数値IDを出力します割り当てられたすでにインストールされているバンドルの数に依存し、異なる場合があるバンドルに。 これでバンドルがインストールされました。次のコマンドでバンドルを開始できます。

> bundle:start 63
Hello World

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

> bundle:stop 63
> bundle:uninstall 63

stop()メソッドのコードに応じて、コンソールに「GoodbyeWorld」が表示されます。

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>

今回は、com.baeldung.osgi.sample.service.definitionパッケージのみがExport-Packageヘッダーを介してエクスポートされたことに注意してください。

このおかげで、 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

各バンドルに割り当てられる識別子番号は異なる場合があることに常に注意してください。

クライアントバンドルを開始しましょう。

> start 65

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

> start 64
Registering service.
Service registered.
Hello John

何が起こるかというと、サービスのBundleActivatorが開始するとすぐに、サービスがプラットフォームに登録されます。 次に、待機していたサービスが利用可能であることをクライアントに通知します。

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

10. 結論

この記事では、OSGiの可能性を理解するのに十分であるという簡単な例を使用して、OSGiの重要な機能について説明しました。

結論として、単一のアプリケーションを無害に更新する必要があることを保証する必要がある場合は常に、OSGiが実行可能なソリューションになる可能性があります。

この投稿のコードは、GitHubにあります。