Jenkins (Hudson)が配布している war ファイルは2つの使い方がある。
- サーブレットコンテナに読み込ませて war ファイルとして利用
-
$ java -jar jenkins.war
ように単体で実行
この2つを実現するための簡単なサンプルを作ったので、実現するための要所を簡単に書く。
- 実行可能 war サンプル: kui/executable-war-sample · GitHub
サンプルのビルド方法
まず実際に動いているのを確認してもらうために、ビルド・実行してほしい。
このサンプルは、比較のために Jetty, Winstone, Tomcat, Glassfish を使って、実行可能 war を構築する。
ビルドには Maven 3 が必要。
git clone git@github.com:kui/executable-war-sample.git
cd executable-war-sample
mvn package
ls **/sample.war
java -jar winstone/target/sample.war
実行可能 war を作るためのポイント
やるべきことは大体3つ:
- 依存関係にサーブレットコンテナを追加
- Main-Class の作成・指定・コピー
- サーブレットコンテナの関連クラスのコピー
Jetty を使った実行可能 war サンプルを例に説明していきます。基本的にはどのコンテナも同じです。
依存関係にサーブレットコンテナを追加
実行時に必要ですが、後述する通り自力でコピーしてしまうので、scope は provided
で大丈夫:
<project >
...
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>9.0.2.v20130417</version>
<scope>provided</scope>
</dependency>
</dependencies>
Main-Class の作成
サーブレットコンテナを立ち上げて、自身を war ファイルとして読み込ませるようにする。例えば Jetty だとこんな感じ:
package jp.k_ui.sample;
import java.net.URL;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
public class Main {
public static void main(String[] args) throws Exception {
int port = Integer.parseInt(System.getProperty("port", "8888"));
URL warLocation = Main.class.getProtectionDomain().getCodeSource()
.getLocation();
WebAppContext webapp = new WebAppContext();
webapp.setWar(warLocation.toExternalForm());
webapp.setContextPath("/");
Server server = new Server(port);
server.setHandler(webapp);
server.start();
server.join();
}
}
このまま $ mvn package
実行して sample.war
を作ったとしても、$ java -jar sample.war
の時に、どのクラスを実行すればよいかわからない。なので、次の設定をする必要がある。
Main-Class の指定
どのクラスの #main(String[])
を呼べば良いのか、という指定をする設定:
<project >
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<archive>
<manifest>
<mainClass>jp.k_ui.sample.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
この状態で $ mvn package
もできるし、$ java -jar sample.jar
で実行も可能。
ただし、$ unzip -l sample.jar
で見るとわかるとおり、jp.k_ui.sample.Main
が無いため実行は失敗する。これをコピーする設定を書く。
Main-Class のコピー
上で梱包されていないことが分かった Main
クラスをコピーする設定:
<project >
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>main-class-placement</id>
<phase>prepare-package</phase>
<configuration>
<tasks>
<move todir="${project.build.directory}/${project.build.finalName}/">
<fileset dir="${project.build.directory}/classes/">
<include name="jp/k_ui/sample/Main.class" />
</fileset>
</move>
</tasks>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
Apache Ant を使って、war 化する前のディレクトリに Main.class
をコピーしている。
この状態で java -jar sample.war
を実行すると、Main
見つからないという文句は消える代わりに、Jetty 関連のクラスが見つからないと言ってくる。
これは先程同様、unzip -l sample.jar
で中身を見てわかるとおり、サーブレットに関わるクラスが war に梱包されていないのが問題になっている。
サーブレットコンテナの関連クラスのコピー
サーブレットコンテナに関わるクラス全てを、これから war 化する前のディレクトリにコピーする設定:
<project >
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.7</version>
<executions>
<execution>
<id>jetty-classpath</id>
<phase>prepare-package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty,javax.servlet</includeGroupIds>
<excludes>META-INF/ECLIPSEF.*</excludes>
<outputDirectory>
${project.build.directory}/${project.build.finalName}
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
...
これでおわり
$ mvn clean package
で、jenkins.war のような、実行可能 war が作成できるようになりました。
なんだか意外と結構な文章量になってしまった。。。
各コンテナの比較は、github の README でやってます。
この記事は、jenkins.war みたいな実行可能な war ファイルの作成 - 電卓片手に で書いた内容とほとんど同じだが、差分ができたのでこっちにも書いてみた