山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

Jar に依存ライブラリを全て含めて配布する

ツールをみんなで共有したいときなど、各自にライブラリ Jar を用意してもらうのはなかなか難しいと思います。単純にダブルクリックやコマンドライン一行で使用できるよう、全てのライブラリを Jar ファイルに含めてしまう方法を紹介したいとおもいます。

このような Jar ファイルを「Fat Jar」と呼びます。

また、開発者が移動や退職したので、ツールのメンテナンスができなくなった、という状況を回避できるよう、Jar ファイルと同じフォルダに、ソースコードも 一緒に圧縮して置いてもらう設定も行います。

ツールの作成

本題とは余り関係ないですが、簡単なツールを作ります。

標準入力で入力された、カンマ区切りファイルをタブ区切りに編集して、標準出力に表示します。

オープンソースの open csv という Csv ファイルの操作が容易になるライブラリを使用します。

Main.java

package sample;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;

import com.opencsv.CSVReader;

public class Main {
	public static void main(String... args) throws Exception {

		final File file;

		// 標準入力から取得したパスを file に格納する。
		try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
			file = new File(br.readLine());
		}

		// 1行ずつ読み込んで、タブ区切りに変換して出力する
		try (CSVReader cr = new CSVReader(new FileReader(file))) {
			String[] arr;
			while ((arr = cr.readNext()) != null) {
				String s = "";
				for (int i = 0; i < arr.length; i++) {
					s = i == 0 ? arr[i] : s + "\t" + arr[i];
				}
				System.out.println(s);
			}
		}
	}
}

これで、例えば以下のようなファイルを text.csv として C:\tmp フォルダ配下に格納します。

aaa,bbb,ccc
ddd,eee,fff
ggg,hhh,iii

実行したときのイメージです。

ファイルのパスを入力してください。:
C:\tmp\text.csv     ← これは手で入力した。
aaa	bbb	ccc
ddd	eee	fff
ggg	hhh	iii

カンマ区切りがタブ区切りに編集されていることが分かります。

通常の Jar ファイルを作成するときの pom.xml はこちら。

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>sample</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.opencsv</groupId>
			<artifactId>opencsv</artifactId>
			<version>4.1</version>
		</dependency>
	</dependencies>

</project>

依存ライブラリを含めた実行可能 Jar にする

今のままだと、作成した Jar を単独で実行できません。

例えば、作成された Jar ファイルを任意のフォルダにコピーし、実行すると次のようになります。

C:\tmp>java -jar sample-0.0.1-SNAPSHOT.jar
sample-0.0.1-SNAPSHOT.jarにメイン・マニフェスト属性がありません

最初にどのメソッドを実行すればいいのか分からないからですね。

じゃあ次のように、main メソッドが含まれたクラスを指定した場合は、どうなるでしょうか?

C:\tmp>java -classpath ./sample-0.0.1-SNAPSHOT.jar org.yyama.Main

実行はできましたが、open csv のライブラリが見つからないのでエラーとなってしまいます。

ファイルのパスを入力してください。:
C:\tmp\text.csv     ← これは手入力
Exception in thread "main" java.lang.NoClassDefFoundError: com/opencsv/CSVReader
        at org.yyama.Main.main(Main.java:22)
Caused by: java.lang.ClassNotFoundException: com.opencsv.CSVReader
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more

それでは、依存ライブラリを含む かつ 実行可能な Jar を作成します。

pom.xml ファイルに次の build タグ以下を追記します。 タグの下に追記します。

	<build>
		<plugins>
			<plugin>
				<artifactId>maven-assembly-plugin</artifactId>
				<version>3.1.0</version>
				<configuration>
					<descriptorRefs>
						<descriptorRef>jar-with-dependencies</descriptorRef>
					</descriptorRefs>
					<archive>
						<manifest>
							<mainClass>org.yyama.Main</mainClass>
						</manifest>
					</archive>
				</configuration>
				<executions>
					<execution>
						<id>sample</id>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

maven-assembly-plugin を使用しています。

以下の部分で、依存ライブラリを全て含む指定をしています。

	<descriptorRefs>
		<descriptorRef>jar-with-dependencies</descriptorRef>
	</descriptorRefs>

そして、以下の部分で実行可能 Jar の main メソッドを含むクラスの指定をします。

	<archive>
		<manifest>
			<mainClass>org.yyama.Main</mainClass>
		</manifest>
	</archive>

ビルドすると、通常の Jar ともうひとつ「-jar-with-dependencies」が付加された Jar ファイルも作成されていると思います。これが「依存ライブラリを全て含んだ実行可能 Jar」です。

実行確認してみます。

c:\tmp>java -jar sample-0.0.1-SNAPSHOT-jar-with-dependencies.jar
ファイルのパスを入力してください。:
C:\tmp\text.csv
aaa     bbb     ccc
ddd     eee     fff
ggg     hhh     iii

動くことが確認できました。

ソースコードも置いてもらう

次にビルド時に使用したソースコードも Jar ファイルと同じ場所に出力してもらいましょう。

この設定は驚くほど簡単でした。

先ほど設定した、descriptorRefs タグの中を次のように変えます。

変更前:

	<descriptorRefs>
		<descriptorRef>jar-with-dependencies</descriptorRef>
	</descriptorRefs>

変更後:

	<descriptorRefs>
		<descriptorRef>jar-with-dependencies</descriptorRef>
		<descriptorRef>src</descriptorRef> <!-- ← 追加 -->
	</descriptorRefs>

これだけで、ビルド時に Jar ファイルと同じフォルダに 3 種類の圧縮ファイルを置いてくれます。

ファイルの例:

  • sample-0.0.1-SNAPSHOT-src.tar.bz2
  • sample-0.0.1-SNAPSHOT-src.tar.gz
  • sample-0.0.1-SNAPSHOT-src.zip

圧縮形式は異なれど、中身は同じなので、好みの1つを配布すればいいでしょう。

まとめ

Java でツールを作ったときなど、チームメンバーに配布するときは、このように依存ライブラリを全て含んだ Jar を ソース付きで配布するといいのではないでしょうか。

おしまい

スッキリわかる Java入門 実践編 第2版 (スッキリシリーズ)

スッキリわかる Java入門 実践編 第2版 (スッキリシリーズ)

やさしいJava 第5版 (「やさしい」シリーズ)

やさしいJava 第5版 (「やさしい」シリーズ)

やさしいJava オブジェクト指向編 (やさしいシリーズ)

やさしいJava オブジェクト指向編 (やさしいシリーズ)