maven-assembly-plugin で実行可能な jar ファイルを作る

Java でバッチプログラムを作る際, class ファイルを jar にまとめてしまうことがよくあります.

しかし, 依存している外部の jar ファイルがある場合は, マニフェストファイルにクラスパスを書くか, --classpath などでクラスパスを指定しなくてはいけないため面倒です.

すべて jar ファイルに同梱してしまいたくなりますが, 外部 jar ファイルを一旦展開してから同梱しないと使えません.

こんな時に, maven-assembly-plugin を使うと便利です.

以下のような, pom.xml を用意します.

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>jp.example</groupId>
  <artifactId>example</artifactId>
  <packaging>jar</packaging>
  <name>Example Batch Application</name>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <sourceDirectory>src/main/java</sourceDirectory>
    <testSourceDirectory>src/test/java</testSourceDirectory>
    <outputDirectory>target/classes</outputDirectory>
    <testOutputDirectory>target/test-classes</testOutputDirectory>
    <defaultGoal>validate</defaultGoal>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>src/test/resources</directory>
      </testResource>
    </testResources>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>2.2</version>
          <configuration>
            <archive>
              <manifest>
                <mainClass>jp.example.command.Main</mainClass>
              </manifest>
            </archive>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptors>
            <descriptor>src/main/assembly/distribution.xml</descriptor>
          </descriptors>
        </configuration>
      </plugin>
    </plugins>
  </build>

 ... snip
	  
</project>

maven-jar-plugin の manifest 要素で, 実行するメインクラスを指定しています.

次に, src/main/assembly/distribution.xml を用意します.

<assembly>
  <id>distribution</id>
  <formats>
    <format>zip</format>
  </formats>
  <includeBaseDirectory>false</includeBaseDirectory>
  <dependencySets>
    <dependencySet>
      <unpack>true</unpack>
      <scope>runtime</scope>
      <outputDirectory>/</outputDirectory>
    </dependencySet>
  </dependencySets>
</assembly>

format は zip にしないと, 自分自身で生成した artifact の jar ファイルをうまく同梱してくれないようです.

includeBaseDirectory を false にすることで, zip ファイルにベースディレクトリを含めなくなります.

dependencySet で unpack を true に, outputDirectory を "/" に指定すると, 依存関係にある外部 jar ファイルを展開し, zip ファイルに含めてくれます.

ここでは, dependency の scope = runtime の jar ファイルのみを同梱します.

Maven2 から assembly:assembly ターゲットを実行すると, 外部 jar ファイルを同梱した ZIP ファイルが生成されます.

$ mvn assembly:assembly

... snip
	  
[INFO] [assembly:assembly]
[INFO] Reading assembly descriptor: src/main/assembly/distribution.xml
[INFO] Processing DependencySet (output=/)
[INFO] Building zip: /Users/nanasess/example/target/example-0.0.1-SNAPSHOT-distribution.zip
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 18 seconds
[INFO] Finished at: Mon Mar 30 23:32:28 JST 2009
[INFO] Final Memory: 37M/80M
[INFO] ------------------------------------------------------------------------

jar も zip も拡張子が違うだけで中身は一緒なので, このままでも実行できてしまいます.

jar の方が良ければ rename してやれば良いと思います.

こんな感じで実行できます.

$ java -jar target/example-0.0.1-SNAPSHOT-distribution.zip
Hello! World!

また, この方法を使うと "/META-INF/maven/groupId/artifactId/pom.properties" に artifactId, groupId, version が出力されます.

こんな感じのメソッドを作ると, コマンドラインからバージョン情報を出力したい時などに便利です.

    /**
     * pom.properties の内容から {@link Properties} を取得する.
     *
     * @return pom.properties から取得した {@link Properties}
     */
    private static Properties getPomProperties() {
        InputStream in = Main.class.getResourceAsStream("/META-INF/maven/groupId/artifactId/pom.properties");
        Properties properties = new Properties();
        try {
            if (in != null) {
                properties.load(in);
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
        return properties;
    }

    /**
     * pom.properties からバージョン情報を取得し, 標準出力へ書き込みます.
     */
    private static void printVersion() {
        Properties properties = getPomProperties();
        String msg = properties.getProperty("artifactId", "<unknown>");
        String version = properties.getProperty("version", "<unknown version>");

        System.out.println(msg + " \"" + version + "\"");
        System.out.println("Java version: " + System.getProperty("java.version", "<unknown java version>"));
        System.out.println("Java home: " + System.getProperty("java.home", "<unknown java home>"));
        System.out.println("Default locale: " + Locale.getDefault() + ", platform encoding: "
                            + System.getProperty("file.encoding", "<unknown encoding>"));
    }
$ java -jar target/example-0.0.1-SNAPSHOT-distribution.zip --version
example "0.0.1-SNAPSHOT"
Java version: 1.6.0_07
Java home: /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home
Default locale: ja_JP, platform encoding: SJIS