GlassfishでWebアプリケーションからOSGiバンドルを利用する

NetBeans IDE の Maven を使用した OSGi 宣言型サービス
このサンプルを動かしてみました。Glassfishは通常のWebアプリケーション(war/ear)と同じようにOSGiバンドルがディプロイできます。このサンプルは、GlassfishにディプロイしたOSGiバンドルが公開する宣言型サービス(DS:Declarative Service)を、別のWebアプリケーションから利用するというものです。宣言型サービスのためのOSGiバンドルとして、

  • サービスAPIバンドル
  • サービスバンドル

の二つのバンドルを作成します。サービスAPIバンドルではインタフェースのみ公開し、サービスバンドルで実装クラスを公開します。WebアプリケーションはOSGiバンドルではなく通常のwar形式でディプロイします。

サービスAPIバンドルの作成

NetBeansでは、Mavenを利用してOSGiバンドル用のプロジェクトが作成できます。新規プロジェクトの作成 -> Maven OSGiバンドルを選択します。

生成されたpom.xmlは、以下のようにpackagingがbundleとなり成果物としてOSGiバンドルが作成できるようになります。

<artifactId>MavenHelloServiceAPI</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>bundle</packaging>
<name>MavenHelloServiceAPI OSGi Bundle</name>

まず、サービスのインタフェースを以下の内容で作成します。

package com.azuki3.mavenhelloserviceapi;

public interface HelloService {
    public String sayHello(String name);
}

そして、プロジェクトのプロパティ->パッケージをエクスポート からパッケージを公開する設定にします。

これによりpom.xmlmaven-bundle-pluginの設定に、Export-Packageの記述が追加されます。

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.0.1</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Export-Package>com.azuki3.mavenhelloserviceapi</Export-Package>
            <Private-Package>com.azuki3.mavenhelloserviceapi.*</Private-Package>
        </instructions>
    </configuration>
</plugin>

作成したバンドルのMANIFEST.MFは以下のようになります。

Manifest-Version: 1.0
Export-Package: com.azuki3.mavenhelloserviceapi
Build-Jdk: 1.6.0_21
Bundle-Version: 1.0.0.SNAPSHOT
Tool: Bnd-0.0.357
Bundle-Name: MavenHelloServiceAPI OSGi Bundle
Bnd-LastModified: 1307453703590
Created-By: Apache Maven Bundle Plugin
Bundle-ManifestVersion: 2
Bundle-SymbolicName: com.azuki3.MavenHelloServiceAPI
Import-Package: com.azuki3.mavenhelloserviceapi

サービスバンドルの作成

このインタフェースを実装するサービスのためのプロジェクトを同様に作成します。そしてプロジェクトの依存リソースとして、

  • 先ほど作成したAPIバンドル
  • org.apache.felix.scr.annotations

を追加します。org.apache.felix.scr.annotationsは、DSを定義するためのXMLファイルを自動生成するツール用のアノテーションです。実際のクラスは以下のようになります。@Componentと@ServiceがDSのためのアノテーションです。

package com.azuki3.mavenhelloservice.impl;

import com.azuki3.mavenhelloserviceapi.HelloService;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;

@Component(name="hello-service")
@Service
public class HelloImpl implements HelloService {
    @Override
    public String sayHello(String name) {
        return "Hello " + name;
    }
}

次にpom.xmlに、このアノテーションを処理するためのscrプラグインを追加します。

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-scr-plugin</artifactId>
    <executions>
        <execution>
            <id>generate-scr-scrdescriptor</id>
            <goals>
                <goal>scr</goal>
            </goals>
        </execution>
    </executions>
</plugin>

この状態でビルドするとOSGI-INFフォルダにDSの定義が記述されたserviceComponents.xmlが作成されます。

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns:scr="http://www.osgi.org/xmlns/scr/v1.0.0">
    <scr:component enabled="true" name="hello-service">
        <implementation class="com.azuki3.mavenhelloservice.impl.HelloImpl"/>
        <service servicefactory="false">
            <provide interface="com.azuki3.mavenhelloserviceapi.HelloService"/>
        </service>
        <property name="service.pid" value="hello-service"/>
    </scr:component>
</components>

OSGiバンドルを使用するWebアプリケーションの作成

NetBeansMaven Webアプリケーションを選択して新規プロジェクトを作成します。プロジェクトの依存リソースにサービスAPIバンドを追加しscopeをprovidedに変更します(OSGiバンドルとして利用するため)。

<dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>MavenHelloServiceAPI</artifactId>
      <version>${project.version}</version>
      <scope>provided</scope>
</dependency>

次に、HelloServiceを利用するサーブレットを作成します。@Resourceアノテーションで、DSを定義する際にname属性で指定した値をmappedNameで指定します。

package com.azuki3.mavenhellowebclient;

import com.azuki3.mavenhelloserviceapi.HelloService;
import java.io.IOException;
import java.io.PrintWriter;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name="HelloClient", urlPatterns={"/HelloClient"})
public class HelloClient extends HttpServlet {

    @Resource(mappedName = "hello-service")
    HelloService helloService;

    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            out.println("<h1>Servlet HelloClient at " + request.getContextPath() + "</h1>");
            out.println(helloService.sayHello("Duke"));
            out.println("</body>");
            out.println("</html>");
        } finally {
            out.close();
        }
    }

    // ...以下省略
}

OSGiバンドルのディプロイ

GlassfishへのOSGiバンドルのディプロイは以下のいずれかの方法で行います。

  • asadminコマンド
  • autodeployフォルダへのjarのコピー

今回はautodeployフォルダにコピーします。NetBeansからGlassfishを起動している場合、autodeployフォルダは以下の場所になります。ここに各バンドルのプロジェクトのtargetフォルダに作成されたjarファイルをコピーします。

C:\Users\ユーザー名\.netbeans\6.9\config\GF3\domain1\autodeploy\bundles

felixの管理コンソール*1から確認すると、以下のようにバンドルがActiveになっていることがわかります。また、NetBeansGlassfishのコンソールにもログが出力されます。

-> find Hello
START LEVEL 1
   ID   State         Level  Name
[ 286] [Active     ] [    1] MavenHelloService OSGi Bundle (1.1.0.SNAPSHOT)
[ 287] [Active     ] [    1] MavenHelloServiceAPI OSGi Bundle (1.0.0.SNAPSHOT)

warを作成してGlassfishにディプロイし、Webアプリケーションにアクセスします。

http://localhost:8080/HelloClient/HelloClient

"hello duke"と表示されたら、正しくDSが呼び出されています。

まとめ

通常のWebアプリケーションから、OSGiの宣言型サービスを簡単に利用することができました。これにより、依存性解決をある程度までOSGiにゆだねることができることになり、いままでのjarによる依存性解決の問題が軽減されます。ただし、依然としてWebアプリケーション自体はOSGiベースではないので以下のような制約もあるようです。

  • アプリケーション起動時に依存ライブラリが利用可能になっている必要がある
  • 動的にバンドル(DS)を差し替えることができない

これは、今回の例でのHelloClientへのHelloServiceのインジェクションについての制約で、OSGi上ではうまくやってくれると思います。
次回は、WebアプリケーションでのOSGiのその他の使い方を試してみたいと思います。

今回のソースは以下にあります。
kenichiro22 / glassfish-osgi-sample / source – Bitbucket

*1:telnet localhost 6666で接続可能