GlassFish Embedded Server のご紹介

この記事はGlassFish Advent Calendar 2013の4日目として新たに書き下ろしたものです。昨日は「GlassFish 4でかんたんクラスタ構築」と題して、GlassFishのクラスタ環境を一番簡単な方法で構築する方法(別名、手抜き工事ともいう)をご紹介しました。


今回はEmbedded GlassFishについてお話しします。

Embedded GlassFishとは、Java SE環境でGlassFishを動作させる仕組みです。Java SE環境で動作させられるサーバーとしてはサーブレット・コンテナのJettyが有名ですが、GlassFishもJettyと同じような使い方が可能です。

1. GlassFishの3つの動作モード

GlassFishには、ドメイン管理サーバー(Domain Administration Server; DAS)、ノード(Node)、埋め込みサーバー(Embedded Server)の3種類の動作モードがあります。いずれも共通基盤であるNucleus上で構築されているため、Java EEサーバーとしての機能に差異はありません。今回の主役は埋め込みーバーですが、その前に3つの動作モードについて簡単に押さえておきましょう。

DASは最も基本的な動作モードで、インストール直後のGlassFishは必ずこのモードになります。GlassFishではJava EEアプリケーションの動作環境をドメインという単位で管理しており、ドメインはスタンドアロン・サーバーで構成することも、複数のサーバー・マシンにまたがるクラスター環境として構成することもできます。DASはドメインに必ず1つ存在し、ドメイン全体を管理する役割を持ちます。クラスター環境ではDASではないサーバーも存在しており、それらの動作モードがノードになります。ノードは自身が所属するドメインのDASによってコントロールされます。DASとノードの詳細については機会を改めてお話しします。

埋め込みサーバーは単体で動作しないかわりにフットプリントを大幅に抑えた動作モードです。埋め込みサーバーはGlassFishのJava EEサーバー機能を単一のJARファイルに集約した、とてもポータブルな配布物となっています。埋め込みサーバー自身は起動する術を持たないため、Java SEアプリケーション上でサーバー機能を起動し、Java EEアプリケーションをデプロイする必要があります。

GlassFishの埋め込みサーバーはあくまでNucleusが持つ動作モードの1つでしかなく、他の動作モードと本質的な部分では差異がありません。何でもないようなことですが、実は重要なことであったりします。

2. 埋め込みサーバーを使ってみよう

ここからは、実際に埋め込みサーバーの使い方について見てゆきましょう。

2.1. 事前準備

まず、埋め込みサーバーのJARファイルを入手します。JARファイル名は glassfish-embedded-all-4.0.jar です。JARファイルは以下のURLから入手できます。

http://mvnrepository.com/artifact/org.glassfish.main.extras/glassfish-embedded-all/4.0

Mavenを使用する場合は以下の依存関係を追加してください。

<dependency>
  <groupId>org.glassfish.main.extras</groupId>
  <artifactId>glassfish-embedded-all</artifactId>
  <version>4.0</version>
</dependency>

また、GlassFishをソースからビルドすると、生成された様々なリソースの中にEmbedded ServerのJARファイルも含まれていますので、それを使用しても構いません。GlassFishのビルド方法については、Unix/Linuxの場合は公式Wikiの記事を、Windowsの場合はこのブログの過去記事をそれぞれ参照してください。

2.2. 全体の流れ

埋め込みサーバーは、概ね以下の流れで実行されます。

  1. GlassFishPropertiesクラスを用いて実行のためのプロパティを作成する。
  2. プロパティを用いてGlassFishクラスのインスタンスを生成する。
  3. GlassFish.start() メソッドでEmbedded Serverを起動する。
  4. CommandRunnnerクラスのインスタンスを取得し、asadminに代わり必要な項目を設定する。
  5. GlassFishインスタンスからDeployerを取得し、アプリケーションをデプロイする。
  6. Embedded Serverが役目を終えたら、GlassFish.stop() で停止する。
  7. 最後にGlassFish.dispose() を呼び出し、クリーンアップを行う。

2.3. 埋め込みサーバーの主な構成要素

GlassFishPropertiesクラス

GlassFish固有のプロパティを設定します。プロパティ自体は任意ですが、少なくともHTTPのリスナー・ポートは設定しないとサーバーが応答できなくなるため、実質的には必須項目です。

GlassFishPropeties glassfishProperties = new GlassFisnProperties();
glassfishProperties.setPort("http-listener", 8080);
glassfishProperties.setPort("https-listener", 8181);

GlassFishクラス

GlassFish本体の初期化、起動、停止、DeployerやCommandRunnerのインスタンスを取得するために使用します。

// GlassFish の初期化と開始
GlassFish glassfish = GlassFishRuntime.bootstrap().newGlassFish(glassfishProperties);
glassfish.start();
// GlassFish の停止とインスタンス破棄
glassfish.stop();
glassfish.dispose();

Deployerクラス

GlassFishにアプリケーションをデプロイまたはアンデプロイします。埋め込みサーバーでは起動するたびにサーバー全体が初期化されるため、毎回デプロイする必要があります。Deployer.deploy() メソッドは、第1引数にデプロイ対象のWARファイルなどをFile、InputStream、URIのいずれかの形で指定します。

// hello.war のデプロイ
Deployer deployer = glassfish.getDeployer();
deployer.deploy(new File("hello.war"), "--name=hello", "--contextroot=hello", "--force=true");

CommandRunnerクラス

埋め込みサーバー上からasadminのコマンドを実行するために使用します。第1引数がサブコマンド名、第2引数は可変長引数で、各サブコマンドの引数を指定します(各引数の区切りはasadminで実行する際にスペースで分割する箇所です)。以下はJDBCコネクションプールを作成するcreate-connecto-poolとJDBCリソースを作成するcreate-jdbc-resourceを連続して実行する例です。

// create-connection-pool サブコマンドと create-jdbc-resource サブコマンドの実行
CommandRunner commandRunner = glassfish.getCommandRunner();
commandRunner.run("create-connection-pool", "--datasourcename", "org.apache.derby.jdbc.EmbeddedDataSource40", "--restype", "javax.sql.DataSource", "--property", "serverName=localhost:PortNumber=1527:DatabaseName=sampledb:User=APP:connectionAttributes=;create\\=true", "SamplePool");
commandRunner.run("create-jdbc-resource", "--connectionpoolid", "SamplePool", "jdbc/sample");

2.4. 補足事項

ベースとなるJava SEアプリケーションの構造によっても異なりますが、埋め込みサーバーが起動した後はDASと同様に localhost:8080 でリクエストを待ち受けます(デフォルトの場合)。ただし、埋め込みサーバーは管理コンソールを持たないため、管理コンソール経由でのサーバーの操作は行えません。また筆者が試した限りでは、ポート4848でHTTPリスナーを起動することが出来なかったため、REST管理チャネルも使用できませんでした(REST管理チャネル自体はNucleusに存在しているため、何らかの方法で使用できるとは思うのですが...今後の課題です)。

asadminはGlassFishのすべてをコントロールできる管理ツールであり、そのasadminのサブコマンドは CommandRunner.run() メソッドで実行することができます。デプロイ以外の各種設定はCommandRunnerで実現可能です。

埋め込みサーバーのクラスの多くは、エラー発生時にキャッチ例外GlassFishExceptionをスローしますので、それを処理してください。

この他にも、既存のdomain.xmlを読み込んでその内容をもとにGlassFishを構成することもできます。

3. 埋め込みサーバーのサンプル

ここでは簡単なサンプルWebアプリケーションと、それをデプロイし実行する埋め込みサーバーのサンプルコードを示します。

3.1. サンプルWebアプリケーション

アプリケーションルートパスのGETリクエストに対してホスト名とIPアドレスの対をXMLまたはJSONで返す iff というWebアプリケーションを作成してみます。iff はMyApplication、Host、TransponserResourceの3つのクラスからなるとても小さなWebアプリケーションです。以下、順に各コードを示します。

/* MyApplication.java */
package jp.coppermine.iff;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

/**
 * JAX-RS をロードするためのクラスです。
 */
@ApplicationPath("/")
public class MyApplication extends Application {
  // GlassFish ではオーバーライドは不要
}
/* Host.java */
package jp.coppermine.iff;

import static javax.xml.bind.annotation.XmlAccessType.FIELD;

import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

/**
 * ホスト名と IP アドレスを保持する JavaBean です。
 */
@XmlRootElement
@XmlAccessorType(FIELD)
@XmlType(propOrder = {"name", "address"})
public class Host {
  @XmlElement
  private String name;
  
  @XmlElement
  private String address;
  
  public Host() { }
  
  public Host(String name, String address) {
    this.name = name;
    this.address = address;
  }
  
  public String getName() {
    return name;
  }
  
  public void setName(String name) {
    this.name = name;
  }
  
  public String getAddress() {
    return address;
  }
  
  public void setAddress(String address) {
    this.address = address;
  }
}
/* TransponderResource.java */
package jp.coppermine.iff;

import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

/**
 * ルートパスへの GET リクエストに対してホスト名と IP アドレスのペアを返します。
 */
@Path("/")
public class TransponderResource {
  @GET
  @Produces("text/plain")
  public String sayHello() {
    return getHost().getName();
  }
  
  @GET
  @Produces({"application/xml", "application/json"})
  public Host respond() {
    return getHost();
  }
  
  private Host getHost() {
    try {
      return new Host(InetAddress.getLocalHost().getHostName(),
        InetAddress.getLocalHost.getHostAddress());
    } catch (UnknownHostException e) {
      return new Host("unknown", "0.0.0.0");
    }
  }
}

3.2. サンプルアプリケーション

埋め込みサーバーを起動・停止するサンプルアプリケーションのコードを以下に示します。このサンプルでは、アプリケーションの終了にシャットダウン・フックを用いており、Ctrl+Cの割り込みシグナルで停止するようにコーディングしています。

/* EmbeddedGlassFish.java */
package jp.coppermine.embeddedglassfish;

import java.io.File;

import org.glassfish.embeddable.Deployer;
import org.glassfish.embeddable.GlassFish;
import org.glassfish.embeddable.GlassFishException;
import org.glassfish.embeddable.GlassFishProperties;
import org.glassfish.embeddable.GlassFishRuntime;

public class EmbeddedGlassFish {
  public static void main(String...args) throws GlassFishException {
    GlassFishProperties glassfishProperties = new GlassFishProperties();
    glassfishProperties.setPort("http-listener", 8080);
    glassfishProperties.setPort("https-listener", 8181);
    
    // 埋め込みサーバーの初期化
    // シャットダウン・フックから呼び出すためfinal宣言します
    final GlassFish glassfish = GlassFishRuntime.bootstrap().newGlassFish(glassfishProperties);
    
    // シャットダウン・フック登録
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          glassfish.stop();
          glassfish.dispose();
          System.err.println("GlassFish を停止しました");
        } catch (GlassFishException e) {
          e.printStackTrace();
        }
      }
    }));
    
    glassfish.start();
    System.err.println("GlassFish を起動しました");
    
    // iff.war はカレントディレクトリに存在する前提
    Deployer deployer = glassfish.getDeployer();
    deployer.deploy(new File("iff.war"), "--name=iff", "--contextroot=iff", "--force=true");
  }
}

エラーハンドリングは完全に手抜きだ。ごめんなさい。

(まあ、構成管理がしっかりしていれば早々エラーになることはないけどね)

サンプルを起動して、iff.warがデプロイされたら、http://localhost:8080/iff にGETリクエストを投げてみるといいです(WebブラウザでこのURLを開いてもOK)。たぶんホスト名か、ホスト名とIPアドレスが対になっているXMLが返ります。たいしておもしろみのないサンプルですが、Hello, worldを返すだけのサンプルよりはマシかと。

4. 参考文献のご紹介―まとめにかえて

埋め込みサーバーに関する情報源は、まとまったものがあまりなくて、Google検索で調べつつ手元の開発環境で試行錯誤することになります。オフィシャルな情報源として、以下の2点を挙げておきます。

  • GlassFish Server Open Source Edition, Embedded Server Guide, Release 4.0 (PDF) - GlassFish 4.0のマニュアルセットのうち、Embedded Serverを扱っている巻です。今回ご紹介しなかったMaven連携や、以前ご紹介した埋め込みEJBコンテナについての解説もあります。全体的に大雑把な印象を受けますが、一応公式ドキュメントです。
  • Embedded GlassFish Javadoc - 埋め込みサーバーで提供されるAPIのドキュメント(Javadoc)です。クラス数が少ないので驚かれるかもしれませんが、その程度で制御できるほどGlassFishの構成はシンプルなのです。

明日は「『GlassFish 4でかんたんクラスタ構築』のちょっとディープな追補」と題して、昨日の記事では紹介できなかったクラスタ構築の少し深い話をしようと思います。ではまた。