1. Qiita
  2. 投稿
  3. Java

jenkins.war のような実行可能 war ファイル作りたい

  • 47
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Jenkins (Hudson)が配布している war ファイルは2つの使い方がある。

  • サーブレットコンテナに読み込ませて war ファイルとして利用
  • $ java -jar jenkins.war ように単体で実行

この2つを実現するための簡単なサンプルを作ったので、実現するための要所を簡単に書く。

サンプルのビルド方法

まず実際に動いているのを確認してもらうために、ビルド・実行してほしい。

このサンプルは、比較のために 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 で大丈夫:

pom.xml
<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 だとこんな感じ:

Main.java
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 ファイルの作成 - 電卓片手に で書いた内容とほとんど同じだが、差分ができたのでこっちにも書いてみた