nikkie-ftnextの日記

イベントレポートや読書メモを発信

『プロになるJava』のHello WorldやbusinessCalendar4Jの例を、Mavenでビルドしたjarで実行する(maven-jar-plugin、maven-assembly-plugin) #projava

はじめに

今はまだ、勇気も自信も全然だから。これが精一杯、nikkieです。

JavaMavenってあるじゃないですか。
触ることがあるんですが、「なんだかよくわからなくてイカツイな」「Pythonと結構違うんだよな」と感じてきました。
このたび「避けずに少し勉強する1」で小さく素振りしてみました。

JavaMavenは全然精通していないので、なにか間違えていたら@ftnextまでお知らせください!

目次

動作環境

  • macOS (M1 Mac)
  • Apache Maven 3.9.3
    • Homebrewでインストール
  • 2つのJava
    • mvn --versionの出力によると、java (openjdk) もbrewで入っている2
      • こちらのjavaのバージョンは20.0.1
    • IntelliJ IDEAでの開発用にOracle Open JDK 20もインストール
      • IntelliJ IDEAでクラスをRunしたときの出力からパスが分かる3
      • こちらのjavaのバージョンは20.0.2

ビルドしたjarファイルはどちらのjavaでも実行できることを確認しています

『プロになるJava』16章でMavenを完全に理解

  • Mavenビルドツール
    • (感想)ビルド(最終成果物のファイルを作る)って、Pythonだとスクリプトで済ませちゃうことも多いから、新・鮮✨
  • pom.xml4で依存ライブラリ(dependency)を宣言する
  • Goals
    • mvn testとかmvn cleanとかmvn packageGoal
    • 表16.2の整理がめちゃめちゃ分かりやすい!
    • 「packageはtestを実行した上で...」、そういうことか!
    • どれもtargetディレクトリを扱っていたのか!

2章のHello WorldMavenでビルドして動かす

IntelliJ IDEAでは動かせているので、Mavenでビルドしたjarを動かしてみます

// src/main/java/projava/JapaneseHolidays2023.java
package projava;

public class JapaneseHolidays2023 {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

mvn packageでビルドしたJARを実行すると
(コマンドは <javaのフルパス> -jar target/projava-maven-practice-1.0-SNAPSHOT.jar

projava-maven-practice-1.0-SNAPSHOT.jarにメイン・マニフェスト属性がありません

助けて〜、GPT-4えもん〜

maven-jar-plugin

GPT-4えもんの回答

mvn package コマンドを使って実行可能なjarファイルを生成するためには、プロジェクトの pom.xml ファイルに Mavenmaven-jar-plugin を設定します。このプラグインを用いることで、jarファイルの生成時にマニフェストファイルを自動的に作成し、その中に 'Main-Class' 属性を設定することが可能です。

※この後に設定例が続くのですが、省略します

回答を参考に以下のように設定。

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>projava.JapaneseHolidays2023</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

この設定により、Mavenでビルドしたjarを動かして「Hello World」の出力を得ました!

16章のbusinessCalendar4Jを使ったプログラムをMavenでビルドして動かす

サンプルコードが公開されています

書籍に沿ってpom.xmlにdependenciesを追加5

    <dependencies>
        <dependency>
            <groupId>one.cafebabe</groupId>
            <artifactId>businessCalendar4j</artifactId>
            <version>1.21</version>
        </dependency>
    </dependencies>

実装(2022年を2023年に変えました)

mvn package6でJARをビルド!
実行すると...

Exception in thread "main" java.lang.NoClassDefFoundError: one/cafebabe/bc4j/BusinessCalendar

再度の、助けて〜、GPT-4えもん〜

maven-assembly-plugin

GPT-4えもんの回答

エラーメッセージ "Exception in thread "main" java.lang.NoClassDefFoundError: one/cafebabe/bc4j/BusinessCalendar" は、JVMJava Virtual Machine)が one.cafebabe.bc4j.BusinessCalendar クラスを見つけられなかったことを示しています。これは、依存関係のあるJAR(このケースではbusinessCalendar4J)がクラスパスに含まれていないために発生します。

そのため、あなたが作成したJARファイルが依存するJARを含んだ"Fat JAR"(または"Uber JAR"とも呼ばれます)を作成する必要があります。そのためには、Mavenmaven-assembly-pluginを使用します。

※この後に設定例が続くのですが、省略します

Fat JARなるものを作っていきます7

    <build>
        <plugins>
            <!-- 省略 -->
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>projava.JapaneseHolidays2023</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

mvn packageでビルドしたJARを実行。
<javaのフルパス> -jar target/projava-maven-practice-1.0-SNAPSHOT-jar-with-dependencies.jar

Holiday{date=2023-01-01, name='元日'}
Holiday{date=2023-01-02, name='休日'}
Holiday{date=2023-01-09, name='成人の日'}
Holiday{date=2023-02-11, name='建国記念の日'}
Holiday{date=2023-02-23, name='天皇誕生日'}
Holiday{date=2023-03-21, name='春分の日'}
Holiday{date=2023-04-29, name='昭和の日'}
Holiday{date=2023-05-03, name='憲法記念日'}
Holiday{date=2023-05-04, name='みどりの日'}
Holiday{date=2023-05-05, name='こどもの日'}
Holiday{date=2023-07-17, name='海の日'}
Holiday{date=2023-08-11, name='山の日'}
Holiday{date=2023-09-18, name='敬老の日'}
Holiday{date=2023-09-23, name='秋分の日'}
Holiday{date=2023-10-09, name='スポーツの日'}
Holiday{date=2023-11-03, name='文化の日'}
Holiday{date=2023-11-23, name='勤労感謝の日'}

動いた!!🙌
(祝日って17日しかないんだ...8

pom.xmlに宣言したバージョンを書き換えて、パッケージのバージョンアップ

『プロになるJava』16章には以下のようにあります。

ライブラリのバージョンアップはversion要素を書き換えるだけで済みます。(Kindle版 p.636)

バージョンアップしてみましょう!
businessCalendar4Jは1.22や1.3.xがリリースされています。
https://search.maven.org/artifact/one.cafebabe/businessCalendar4j

最新まで上げてみたところ、1.3.xではAPIが破壊的に変更されたようでコンパイルできず。
今回は1.21 -> 1.22に上げてビルドしました。

    <dependencies>
        <dependency>
            <groupId>one.cafebabe</groupId>
            <artifactId>businessCalendar4j</artifactId>
-            <version>1.21</version>
+            <version>1.22</version>
        </dependency>
    </dependencies>

pom.xmlの宣言を変えてビルド(mvn package)というのは新鮮です〜

終わりに

Maven、完全に理解したかもしれない〜✌️
慣れ親しんだPythonのpipとは所作が結構違いますが、これはもう完全に別物として考えるのが私にはしっくり来ています。
ちょっとだけ触ったRust(Cargo)もMavenに近いかもな〜9

ありがとう、『プロになるJava』、そして、GPT-4えもん!
GPT-4えもんに今回はめっちゃ助けられましたし、「これってどうやるんだろう」と書籍からちょっと脱線するというムーブが成功したことに大満足です。

ちなみに、今回のプロンプト

あなたはJavaの開発経験が豊富なエンジニアです。新人開発者からの質問に根気よく丁寧に回答してください。

新人開発者「javaのjarファイルの実行について教えてください」

新人開発者「...」でひたすら聞きまくりました(最後の方は面倒に感じて普通に聞いていた)

そして、Mavenを使った開発、ニジガク🌈要素あるよね!?
ときめいちゃった! ぽむー🎀


  1. こちらの「追伸」です
  2. /opt/homebrew/Cellar/openjdk/20.0.1/libexec/openjdk.jdk/Contents/Home/bin/java
  3. ~/Library/Java/JavaVirtualMachines/openjdk-20.0.2/Contents/Home/bin/java
  4. あれ... もしかして... ぽむー🎀
  5. https://github.com/projava17/examples/blob/78c3d6822824b1338a3ea185b7921b846c41e9eb/part-5/chapter-16-build-tool/pom.xml#L16-L22
  6. 実際はmvn clean packageでtargetディレクトリを消して(自分がハマりづらくして)からビルドしています
  7. mainClassの宣言はmaven-assembly-pluginでも行いました。maven-jar-pluginをコメントアウトしても動きます
  8. 数え間違えたかと思ってwc -lしても17日でした。現実は非情である