クラウド環境を勉強していると、実際に動くアプリで試したくなることがある。
久しぶりにJavaでサンプルを作ってみようかなと思ってみたのだが、どのサンプルを見てもspring bootやmavenやgradleが利用されており、どれも慣れていないので、まずはmavenでも覚えようかな。そのための手始めとして、簡単なpom.xmlを理解しつつ、簡単なjavaプログラムでコンパイル&配布パッケージの作成までやってみた。
以下の情報を参考にしつつ、私が理解できていない箇所は適宜調べて記載した。
簡単なJavaプログラム
目的はmavenを理解することなので、Javaプログラムは小さいもので済ませる。
package hello;
public class HelloWorld {
public static void main(String[] args) {
Greeter greeter = new Greeter();
System.out.println(greeter.sayHello());
}
}
package hello;
public class Greeter {
public String sayHello() {
return "Hello, World!";
}
}
pom.xml
pom.xmlファイルとは、mavenのプロジェクト定義ファイルのことで、プログラムの名前、バージョン、依存関係などを定義するファイル。プロジェクトのルートフォルダーに保存する。
以下は簡単なpom.xmlファイルで、1つだけシェーディング用のプラグインを追加し、外部ライブラリは使用していないので依存関係は含まれていない。(外部ライブラリの定義やリポジトリについてはSpring Bootをやり始める時に改めて確認する)
調査しつつ、xmlではなくjsonにして欲しかったと思った。。
<?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> <https://maven.apache.org/xsd/maven-4.0.0.xsd>">
<!-- POM自体のモデルバージョン(固定) -->
<modelVersion>4.0.0</modelVersion>
<!-- グループや組織を表す文字列、通常は逆ドメイン名を使用、パッケージ名とは異なる -->
<groupId>com.juncleit</groupId>
<!-- 生成ファイルのファイル名 -->
<artifactId>simplemaven</artifactId>
<!-- 生成ファイル拡張子 "jar" or "war" -->
<packaging>jar</packaging>
<!-- 生成ファイルのバージョン -->
<version>0.1.0</version>
<!-- javaコンパイラーに指定する言語仕様とランタイムのバージョン -->
<properties>
<!-- ソースコードで使用している言語バージョン -->
<maven.compiler.source>1.8</maven.compiler.source>
<!-- 生成ファイルの互換ランタイムバージョン -->
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- ビルド中に利用されるプラグインの定義領域 -->
<build>
<plugins>
<plugin>
<!-- 使用するプラグインのgroupId, artifactId, versionは必須要素 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<!-- shadeプラグインを実行するフェーズの指定-->
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<!-- shadeプラグインの設定 -->
<configuration>
<transformers>
<!-- shadeプラグインの実行クラス=Manifestの書き換え -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<!-- ManifestファイルのMain-Classをhello.HelloWorldクラスに置き換え -->
<mainClass>hello.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
pluginとは
mavenのプラグインとは、ライフサイクルもしくはphaseで実行できる処理(=goal)が定義されたmojoのこと。1つのプラグインには複数の処理(goal)が定義されている。
以下、参考にした情報。
executions/executionタグとは
buildライフサイクルの特定フェーズにおいて、カスタム処理をmojoプラグインで実行するためのタグ。executionsには複数のexecutionを含めることができ、各executionには1つのmojoを含めることができる。executionには、実行対象のフェーズを示すphaseタグを指定できる。
1つのmojoは複数のexecutionで指定することができるので、複数のフェーズで異なるプロパティを指定したmojoを実行することができる。
サンプルpom.xmlの例では、ManifestResourceTranfomerというシェーディングを行うmojoが指定されており、packageフェーズでMANIFESTファイルのMain-Classを定義する。
mojoとは
Maven plain Old Java Objectの略。各mojoというのは実行可能なgoalを定義しており、mavenプラグインは1つ以上のmojoで構成されている。
buildライフサイクルとは
mavenのbuild ライフサイクルは、いくつかのフェーズで構成されており、以下の順番にフェーズを実行する。
- compile : ソースコードのコンパイル
- test-compile : テストソースコードのコンパイル
- test : テストの実行
- package : 配布ファイル(jar, war, ear, etc..)の作成
- integration-test : 結合テスト用の環境にデプロイ
- install : パッケージをローカルリポジトリへの登録
- deploy : パッケージをリモートリポジトリへ登録
mavenでbuildライフサイクルを実行する時に実行対象のフェーズを指定できる。例えば、packageフェーズを指定すると、compileからpackageまでのフェーズを順番に実行する。(通常デフォルトのフェーズはpackage)
以下、参考にした情報。
phaseとは
buildライフサイクル、cleanライフサイクルを構成する一つのタスク。各phaseは一連のgoalで構成されている。
以下、参考にした情報。
goalとは
各phaseを構成するプラグインの処理。1つのphaseを実行するとき、そのphaseにバインドされているプラグインのgoalが順番に実行される。
例えば、compile phaseにはデフォルトでmaven-compilerプラグインが指定されており、maven-compilerプラグインには「compile」という処理(=goal)が含まれている。
以下、compile phaseに含まれるプラグインとgoalの確認結果。
> mvn help:describe -Dcmd=compile
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< org.springframework:gs-maven >--------------------
[INFO] Building gs-maven 0.1.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- help:3.4.0:describe (default-cli) @ gs-maven ---
[INFO] **'compile' is a phase corresponding to this plugin:
org.apache.maven.plugins:maven-compiler-plugin:3.10.1:compile**
上記を要約すると、compileというphaseには、compilerというmavenプラグインのcompileというgoalが定義されており、compileフェーズの実行=mavenプラグインのcompileが実行されると解釈する。
以下、参考にした情報。
u-ber jarとは
依存関係のある外部ライブラリを1つのjarファイルにまとめて、以下のように単純なコマンドで実行できるようにしたjarファイルのことをu-ber.jarと呼ぶ。
> java -jar target.jar
例えば、spring-bootでRESTfulなWEBサービス用のjarファイルを作成した場合、jarファイルにはSpring-Boot関連ライブラリ、Tomcat、Jackson、その他多くの外部ライブラリが含まれ、上記コマンドを実行することでTomcatが起動し、ブラウザからのリクエストに応答できるWEBアプリケーションが起動する。作成したjarファイルを配布する場合、利用者は依存関係のある全ての外部ライブラリを自分で準備することなく、簡単に利用することができるようになる。
以下、参考にした情報。
shade plugin(シェーディング)とは
最もシンプルなmavenのpom.xmlファイルと言いつつも、上記で定義したファイルにはManifestファイルのShade pluginが使用されているが、これは先に説明したu-ber.jarを作成する場合にいくつかの考慮が必要な場合に便利なプラグイン。自分のプロジェクトファイルの他に多くの外部ライブラリを1つのjarファイルにマージすると多くの箇所でコンフリクトが発生する可能性がある。これを回避するための処理をshadeもしくはシェーディングという。
shade pluginにはいくつかのTransformerというクラスが定義されており、例えば、パッケージ名が衝突するようなライブラリを利用している場合はパッケージ名をリネームし、参照箇所も一緒に書き換える処理などが定義されている。
Shade pluginに含まれる各Transformerの用途が以下に定義されている。
最もシンプルなpom.xmlファイルで使用されているManifestResourceTransformerというクラスは、MANIFESTファイルのエントリーを設定するための処理が定義されている。
以下、参考にした情報。
シェーディングされた結果を確認
mvnコマンドを実行した後、ManifestResourceTranformerによって置き換えられた(シェーディングされた)結果を確認してみる。
上記のpom.xmlでは、package phaseでシェーディング処理が実行される。シェーディングされると、targetディレクトリには”original”という接頭辞が含まれるファイルが生成されているが、これがシェーディングされる前のファイルで、”ogirinal”という接頭辞がないファイルがシェーディングされた後のファイル。
% ls
classes/ maven-archiver/
generated-sources/ maven-status/
gs-maven-0.1.0.jar original-gs-maven-0.1.0.jar
それぞれのjarファイルの内容を確認すると、MANIFEST.MFファイルだけサイズが異なっている。(表示されるファイルの順番が異なる理由はわからない)
% tar -tvf gs-maven-0.1.0.jar
-rw-rw-r-- 0 0 0 111 1 1 11:52 META-INF/MANIFEST.MF
drwxrwxr-x 0 0 0 0 1 1 11:52 META-INF/
drwxrwxr-x 0 0 0 0 1 1 11:52 hello/
-rw-rw-r-- 0 0 0 648 1 1 11:52 hello/HelloWorld.class
-rw-rw-r-- 0 0 0 370 1 1 11:52 hello/Greeter.class
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/org.springframework/
drwxrwxr-x 0 0 0 0 12 31 23:00 META-INF/maven/org.springframework/gs-maven/
-rw-rw-r-- 0 0 0 1177 12 31 23:00 META-INF/maven/org.springframework/gs-maven/pom.xml
-rw-rw-r-- 0 0 0 62 1 1 11:52 META-INF/maven/org.springframework/gs-maven/pom.properties
% tar -tvf original-gs-maven-0.1.0.jar
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/
-rw-r--r-- 0 0 0 81 1 1 11:52 META-INF/MANIFEST.MF
drwxr-xr-x 0 0 0 0 1 1 11:52 hello/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/org.springframework/
drwxr-xr-x 0 0 0 0 1 1 11:52 META-INF/maven/org.springframework/gs-maven/
-rw-r--r-- 0 0 0 648 1 1 11:52 hello/HelloWorld.class
-rw-r--r-- 0 0 0 370 1 1 11:52 hello/Greeter.class
-rw-r--r-- 0 0 0 1177 12 31 23:00 META-INF/maven/org.springframework/gs-maven/pom.xml
-rw-r--r-- 0 0 0 62 1 1 11:52 META-INF/maven/org.springframework/gs-maven/pom.properties
それぞれのMANIFEST.MFファイルを確認すると、シェーディングされたjarファイルのMANIFEST.MFには「Main-Class: hello.HelloWorld」というメインクラスが指定されている。
# gs-maven-0.1.0.jarのMANIFEST.MFファイルの内容
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
Main-Class: hello.HelloWorld
# original-gs-maven-0.1.0.jarのMANIFEST.MFファイルの内容
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk-Spec: 21
MANIFEST.MFにメインクラスの指定があると、javaコマンドでプログラムを実行する際に、メインクラスを指定する必要がなく、「java -jar *.jar」という単純なコマンドでプログラムを起動できる。
コメント