ずっと Gradle 使ってたけど、お仕事で Maven をゴリゴリに使わないといけなくなったのでお勉強。
Maven とは
Java のビルドツール。OSS。
Apache Ant に代わるものとして作られたらしい。
読み方は「メイヴン」または「メイヴェン」(自分はメイヴン派)。
結構昔からあるけど1、現在も開発が続けられているし、 Maven を採用しているプロジェクトも多い印象。
2020年現在、Java のビルドツールといえば Maven か Gradle が選ばれることが多い(と思う)。
2020年現在のメジャーバージョンは3。
Maven 1 と 2 は互換性が無いが、2 と 3 は互換性が保たれている。
環境
>mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: ...\bin\..
Java version: 11.0.6, vendor: AdoptOpenJDK, runtime: ...
Default locale: ja_JP, platform encoding: MS932
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
インストール
※JDK のインストール方法は割愛。
-
公式サイトからアーカイブ(
apache-maven-X.X.X-bin.zip
)をダウンロードしてくる - 適当なところで解凍する
`-apache-maven-X.X.X/
|-bin/
|-boot/
|-conf/
|-lib/
|-LICENSE
|-NOTICE
`-README.txt
※解凍先のフォルダ(上の例でいうと apache-maven-X.X.X
)を、以後は %MAVEN_HOME%
と記述する。
-
%MAVEN_HOME%\bin
にパスを通す - 以下のコマンドを実行してバージョンの情報が出たらインストール完了
>mvn --version
Apache Maven X.X.X (...)
...
設定ファイル
プロジェクトをまたがって共通な設定は、以下のいずれかのファイルに記述することができる。
%MAVEN_HOME%\conf\settings.xml
-
%USERPROFILE%\.m2\settings.xml
2
前者のファイルに設定した内容は、ユーザをまたがってすべてのプロジェクトで共通となる。
後者のファイルに設定した内容は、そのユーザ内でプロジェクトをまたがって共通となる。
前者のファイルはダウンロードした zip の中に入っているが、全ての設定は空になっていてコメントで説明が書かれている。
後者のファイルは最初は存在しないので、前者のファイルをコピーして作ると良い。
プロキシの設定
<settings ...>
...
<proxies>
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
</proxies>
...
</settings>
必要な設定を書き換えて、前述のいずれかの settings.xml
に記述する。
Hello World
プロジェクトを生成する
- 適当なフォルダでコマンドラインを開き、以下のコマンドを実行する。
> mvn archetype:generate
- 初回は色々ダウンロードなどの処理が走った後に、次のような一覧が表示される。
...
Choose archetype:
1: internal -> org.appfuse.archetypes:appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate, Spring and JSF)
2: internal -> org.appfuse.archetypes:appfuse-basic-spring (AppFuse archetype for creating a web application with Hibernate, Spring and Spring MVC)
3: internal -> org.appfuse.archetypes:appfuse-basic-struts (AppFuse archetype for creating a web application with Hibernate, Spring and Struts 2)
...
16: internal -> org.apache.maven.archetypes:maven-archetype-quickstart ()
...
57: internal -> org.fusesource.scalate.tooling:scalate-archetype-empty (Generates a Scalate empty web application)
58: internal -> org.fusesource.scalate.tooling:scalate-archetype-guice (Generates a Scalate Jog web application)
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 16:
-
Choose archetype
と表示され、使用するアーキタイプの番号を入力するように促される - とりあえずデフォルトとなっている 16 (
org.apache.maven.archetypes:maven-archetype-quickstart
) を選択してみる(そのままEnter を入力すれば、デフォルトの番号が選択される)
Define value for property 'groupId': : 【com.example】
Define value for property 'artifactId': : 【hello-world】
Define value for property 'version': 1.0-SNAPSHOT: :【】
Define value for property 'package': com.example: :【】
Confirm properties configuration:
groupId: com.example
artifactId: hello-world
version: 1.0-SNAPSHOT
package: com.example
Y: : 【y】
- 作成するプロジェクトの基本情報の入力が促されるので、適当に入力していく
- 上記例は、
【】
で囲った部分がキーボードで入力した内容となる(空の【】
は何も入力せずに Enter した箇所) - それぞれの意味はおいおい説明
- 上記例は、
- 最後に入力した情報で作成してよいか聞かれるので、問題なければ
y
を入力して Enter -
BUILD SUCCESS
と表示されれば成功
生成されたプロジェクトの確認
- カレントフォルダに
artifactId
で指定した名前と同じ名前のフォルダが作られている- 上の例で作った場合は
hello-world
というフォルダが作られている
- 上の例で作った場合は
-
hello-world
フォルダの中身は次のようになっている
hello-world/
|-pom.xml
`-src/
|-main/java/
| `-com/example/
| `-App.java
`-test/java/
`-com/example/
`-AppTest.java
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>hello-world</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
...
</plugins>
</pluginManagement>
</build>
</project>
package com.example;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println( "Hello World!" );
}
}
- 簡単な Hello World プログラムのプロジェクトが生成されている
コンパイルして実行する
- 以下のコマンドを実行してソースコードをコンパイルする
> mvn compile
- これまた、初回は色々ダウンロード処理が走る(2回目以降はダウンロードがなくなるので速くなるはず)
-
BUILD SUCCESS
と表示されたら成功 -
hello-world/target/classes/
の下に、App.java
のコンパイル結果が出力されている - 以下のコマンドで実行する
> java -cp target\classes com.example.App
Hello World!
説明
アーキタイプ
- 最初の
mvn archetype:generate
は、アーキタイプと呼ばれるプロジェクトをひな形から自動生成する仕組みを実行している- ここでは、最もシンプルな
maven-archetype-quickstart
を選択してプロジェクトを自動生成している
- ここでは、最もシンプルな
POM
Maven – Introduction to the POM
Maven の設定は pom.xml という XML ファイルで記述する。
POM は、 Project Object Model の頭文字を取った略となっている。
Maven はビルド対象をプロジェクトという単位で管理する。
POM は、そのプロジェクトについての様々な設定を記述するためのファイルとなる。
Super POM
POM には親子関係があり、全ての POM の最上位の親としてSuper POMというものが存在する。
例えば、最新の Super POM は次のページで確認できる。
Maven Model Builder – Super POM
プロジェクトの POM に設定がない場合は、基本的に親 POM の設定が継承されるようになっている。
つまり、 Super POM はすべてのプロジェクトで適用されるデフォルトの設定が記述された POM ということになる。
最小構成の POM
最小構成の POM を作るとすると、 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
- 一番上に
<project>
タグを記述する - その中には、少なくとも次の4つのタグを記述しなければならない
<modelVersion>
<groupId>
<artifactId>
<version>
-
<modelVersion>
は、 POM のバージョンを指定している- これは基本的に変わることが無いので、とりあえずおまじない的にこの値を指定しておけばいいと思う
-
<groupId>
,<artifactId>
,<version>
の3つは、プロジェクトを一意に識別するための情報を定義している- プロジェクトの完全修飾名は、
<groupId>:<artifactId>:<version>
となる - つまり、上記設定の場合は
example:hello:1.0.0
がこのプロジェクトの完全修飾名となる -
<groupId>
-
groupId
には、そのプロジェクトが属する組織や上位プロジェクトを識別できる名前を指定する - 例えば、 Maven が提供する多くのプラグインは
org.apache.maven.plugins
というgroupId
が指定されている -
.
は付いてもついていなくてもいい(junit
などは付いていない) -
groupId
の値と、そのプロジェクトの Java パッケージ構成が一致している必要はないが、一致させておいたほうが混乱が少なくて無難
-
-
<artifactId>
- そのプロジェクトを識別する名前を指定する
- 例えば、 Maven が提供しているコンパイラプラグインは
maven-compiler-plugin
というartifactId
となっている
-
<version>
-
version
は、そのプロジェクトのバージョンを指定する -
groupId
とartifactId
によってプロジェクトが一意に特定され、さらにversion
によってバージョンが特定されることになる
-
- プロジェクトの完全修飾名は、
- この pom.xml に記述されていない設定は、基本的に Super POM から継承される3
- つまり、記述されていない
<repository>
の設定は、<url>https://repo.maven.apache.org/maven2</url>
が採用されていることになる - ただ、実は Super POM に記述されてないけどデフォルトで採用されている設定も存在している(
<packaging>
とか)- そのへんの話はおいおい
- つまり、記述されていない
開発バージョンを表すプレフィックス
- アーティファクトのバージョン番号について、決まった採番のルールはない(たぶん)
- ただし、末尾に
-SNAPSHOT
とついたバージョンについては、特別な意味がある -
-SNAPSHOT
プレフィックスが付いたバージョンは、それが開発中のバージョンであることを表している - 開発中のバージョンは、アーティファクト(jar)の中身が更新される可能性があることを表している
- 一方で、 SNAPSHOT が付いていないバージョンは基本的にリリースバージョンを表しており、中身が更新されない(というのがお約束)
- あくまでお約束なので、仕組みで縛られているわけではない
- 後述するリポジトリ(Nexus)の機能で、リリースバージョンは更新不可、SNAPSHOTは更新可にするような制御は可能
変数
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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>project.groupId = ${project.groupId}</echo>
<echo>project.artifactId = ${project.artifactId}</echo>
<echo>project.version = ${project.version}</echo>
<echo>project.build.directory = ${project.build.directory}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 何やらいきなりいっぱい現れたが、ここで重要なのは次の箇所になる
<echo>project.groupId = ${project.groupId}</echo>
<echo>project.artifactId = ${project.artifactId}</echo>
<echo>project.version = ${project.version}</echo>
<echo>project.build.directory = ${project.build.directory}</echo>
- いくつかの変数の値を出力(echo)している
- この周りにいっぱい書いてあるやつは、 echo ができるようにするためにプラグインを追加している
- これを実行すると、次のようになる
> mvn antrun:run
...
[echo] project.groupId = example
[echo] project.artifactId = hello
[echo] project.version = 1.0.0
[echo] project.build.directory = F:\tmp\maven\hello\target
...
-
${変数の参照式}
で記述した部分が、式の評価結果に置き換えられて出力されているのが分かる - この例で参照している変数は、全て**プロジェクトモデル変数(Project Model Variable)**と呼ばれるものになる
- 要するに、 pom.xml 内の
<project>
配下のタグの値を参照している-
project.version
は、<project><version>
の値を参照している -
project.build.directory
は、この pom.xml の中にはないが Super POM の中で宣言されている
-
式の構文
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>project.class = ${project.class}</echo>
<echo>project.getClass() = ${project.getClass()}</echo>
<echo>plugins[0].artifactId = ${project.build.plugins[0].artifactId}</echo>
<echo>plugins[1].artifactId = ${project.build.plugins[1].artifactId}</echo>
<echo>plugins[2].artifactId = ${project.build.plugins[2].artifactId}</echo>
</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jdeps-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
</project>
> mvn antrun:run
...
[echo] project.class = class org.apache.maven.model.Model
[echo] project.getClass() = ${project.getClass()}
[echo] plugins[0].artifactId = maven-antrun-plugin
[echo] plugins[1].artifactId = maven-jdeps-plugin
[echo] plugins[2].artifactId = ${project.build.plugins[2].artifactId}
-
${...}
の中に記述する式は、基本的にドット (.
) 区切りでオブジェクトのプロパティを参照していけるようになっている-
foo
という名前のプロパティを参照すると、裏ではgetFoo()
(またはisFoo()
)というメソッドが実行されている - つまり、 Getter さえあれば対応するプロパティ名で参照できる(
class
も参照できる)
-
- ただし、参照できるのはプロパティのみで、メソッドを直接実行したりはできない
- プロパティが配列または
List
の場合は、角括弧 ([]
) を使ってインデックス参照ができる - 式が正常に評価できない場合は、ただの文字列として処理される
-
project
変数の実体は、 org.apache.maven.model.Model というクラスのインスタンスとなっている- この
Model
インスタンスが ReflectionValueExtractor に渡されることによって式が評価されている
- この
Mapの参照
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>${project.build.pluginsAsMap(org.apache.maven.plugins:maven-antrun-plugin).id}</echo>
<echo>${project.build.pluginsAsMap(org.apache.maven.plugins:maven-jdeps-plugin).id}</echo>
</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jdeps-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
</project>
> mvn antrun:run
...
[echo] org.apache.maven.plugins:maven-antrun-plugin:1.8
[echo] org.apache.maven.plugins:maven-jdeps-plugin:3.1.2
- プロパティが
Map
の場合、fooMap(<key>)
という形でキー指定の参照ができる-
<key>
はダブルクォーテーション ("
) などで括る必要はなく、そのままString
のキーとして使用される
-
-
pluginsAsMap
というプロパティは、 PluginContainer というクラスに定義されたメソッドを参照している- この
Map
は、キーに Plugin#getKey() の結果、値に該当するPlugin
インスタンスが設定されている-
Plugin#getKey()
は、そのプラグインのgroupId
とartifactId
をコロン (:
) でつなげた値を返す
-
-
project.build
で参照できる Build クラスは、このPluginContainer
を継承している
- この
- このように、一部のクラスには
List
をMap
形式に変換したプロパティを提供しているものがある
各クラスのプロパティ
Model
から参照できる各クラスやプロパティの全体像をクラス図でまとめた。
- 赤線が継承
- 青線が単一の参照
- 緑線が List での参照
特殊変数
- プロジェクトモデルには含まれていないが、特別に定義されていて参照が可能な変数がいくつか存在する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
...
<target>
<echo>project.basedir = ${project.basedir}</echo>
<echo>project.baseUri = ${project.baseUri}</echo>
<echo>maven.build.timestamp = ${maven.build.timestamp}</echo>
</target>
...
</build>
</project>
[echo] project.basedir = F:\tmp\maven\hello
[echo] project.baseUri = file:///F:/tmp/maven/hello/
[echo] maven.build.timestamp = 2020-03-04T13:45:10Z
- 次の3つは、**特殊変数(Special Variables)**として暗黙的に定義されている変数となる
-
project.basedir
- プロジェクト自体のフォルダ
-
project.baseUri
-
project.basedir
を URI で表したもの
-
-
maven.build.timestamp
- 実行時のタイムスタンプ(UTC)
-
タイムスタンプのフォーマットを指定する
-
maven.build.timestamp.format
というプロパティを宣言することで、maven.build.timestamp
のフォーマットを任意に指定することができる - なお、フォーマットの書式は SimpleDateFormat に従う
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<properties>
<maven.build.timestamp.format>yyyy/MM/dd HH:mm:ss</maven.build.timestamp.format>
</properties>
<build>
...
<echo>maven.build.timestamp = ${maven.build.timestamp}</echo>
...
</build>
</project>
[echo] maven.build.timestamp = 2020/03/04 13:49:49+00:00
プロパティ
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<properties>
<foo>FOO!!</foo>
<fizz.buzz>FIZZ.BUZZ!?</fizz.buzz>
<hoge-fuga>HOGE-FUGA??</hoge-fuga>
</properties>
<build>
...
<echo>foo = ${foo}</echo>
<echo>fizz.buzz = ${fizz.buzz}</echo>
<echo>hoge-fuga = ${hoge-fuga}</echo>
<echo>FOO = ${FOO}</echo>
...
</build>
</project>
[echo] foo = FOO!!
[echo] fizz.buzz = FIZZ.BUZZ!?
[echo] hoge-fuga = HOGE-FUGA??
[echo] FOO = ${FOO}
-
<properties>
タグの中で独自の変数(プロパティ)を宣言できる -
-
や.
を名前に含めることが可能 - 大文字・小文字は区別される
環境変数
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
...
<echo>hoge = ${env.hoge}</echo>
<echo>Hoge = ${env.Hoge}</echo>
<echo>HOGE = ${env.HOGE}</echo>
...
</build>
</project>
> set Hoge=Hello
> mvn antrun:run
...
[echo] hoge = ${env.hoge}
[echo] Hoge = ${env.Hoge}
[echo] HOGE = Hello
...
$ export Hoge=Hello
$ mvn antrun:run
...
[echo] hoge = ${env.hoge}
[echo] Hoge = Hello
[echo] HOGE = ${env.HOGE}
-
${env.環境変数名}
で、 OS の環境変数の値を参照できる - Windows 上で動く場合、
env
の変数名はすべて大文字で正規化されている- つまり、 Windows 上で宣言されている環境変数の名前がたとえ
Path
であっても、 pom.xml 上で参照するときは${env.PATH}
と大文字で記述しなければならない
- つまり、 Windows 上で宣言されている環境変数の名前がたとえ
- あくまで Windows 上での話で、 Linux 上で動かす場合は大文字・小文字を区別したそのままの名前で指定する
- まぁ、環境変数は全部大文字で宣言するのが一般的だと思うので、 pom.xml も全部大文字で書いておけば事故ることはないと思う
システムプロパティ
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<properties>
<foo.bar>foo-bar</foo.bar>
<fizz.buzz>fizz-buzz</fizz.buzz>
</properties>
<build>
...
<echo>java.version = ${java.version}</echo>
<echo>java.vm.vendor = ${java.vm.vendor}</echo>
<echo>foo.bar = ${foo.bar}</echo>
<echo>fizz.buzz = ${fizz.buzz}</echo>
...
</build>
</project>
> mvn antrun:run -Dfoo.bar=FOO-BAR
...
[echo] java.version = 11.0.6
[echo] java.vm.vendor = AdoptOpenJDK
[echo] foo.bar = FOO-BAR
[echo] fizz.buzz = fizz-buzz
- Java のシステムプロパティ(
System.getProperties()
で取得できる値)を、そのまま${システムプロパティ名}
で参照できる -
<properties>
で同じ名前のプロパティが宣言されている場合は、システムプロパティで指定した値が優先される
リポジトリ
- Maven の大事な構成要素の1つに**リポジトリ(Repository)**がある
- Maven では、作成したプロジェクトの成果物(アーティファクト)をリポジトリに保存して管理する
- アーティファクトの実体は、普通はそのプロジェクトをビルドしてできた jar ファイル
セントラルリポジトリ
- リポジトリには2種類ある
- ローカルリポジトリ
- リモートリポジトリ
- デフォルトで使用されるリモートリポジトリとして、セントラルリポジトリというものが存在する
- ↑のリンクをブラウザで開けば、様々なサブディレクトリが存在することが分かる
- ディレクトリを適当に辿っていくと、最終的に jar ファイルが配置されたディレクトリにたどり着く
- 例えばこれは、Spring Framework の core の ver 5.2.4.RELEASE が配置されたディレクトリになる
- このように、セントラルリポジトリには世界中の様々な OSS のアーティファクトが収められている
- セントラルリポジトリの管理は Sonatype という会社が行っている
- 申請を行えば、誰でもセントラルリポジトリで自分の OSS を公開できる(無料)
- ただし、申請は英語
- 申請の単位は
groupId
ごと - 「Maven セントラルリポジトリ 公開 手順」とかで検索すれば、いろいろ解説記事が出てくる
- セントラルリポジトリを直接ブラウザで開いたものは検索がしづらいので、普通は Maven Repository: Search/Browse/Explore のような検索サイトを使う
- もしくは、使用したいライブラリの公式ページに書いてある情報を参照する
依存関係の解決
- Maven の強力な機能の1つとして、依存関係の解決がある
- pom.xml では、そのプロジェクトが使用するアーティファクトを依存関係として次のように定義できる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
</project>
-
<dependencies>
の下の<dependency>
が、1つのアーティファクトを指している - このように宣言すると、 Maven はプロジェクトをビルドするときに自動的に
<dependencies>
で宣言したアーティファクトをリモートリポジトリからダウンロードしてきてくれて、クラスパスに追加してくれる- つまり、ライブラリを手で落としてくる手間が省ける
- さらに、リポジトリに格納されたアーティファクトには、それ自身の pom.xml も格納されている
- もしプロジェクトで指定したアーティファクトの pom.xml に、さらに
<dependencies>
があった場合、 Maven はその依存アーティファクトも自動的にダウンロードしてくれる - つまり、芋づる式にすべての依存関係を解決してくれる仕組みになっている
- もしプロジェクトで指定したアーティファクトの pom.xml に、さらに
- Maven は、この仕組みのおかげで依存ライブラリの管理が非常に楽になっている
- Maven の前身である Apache Ant は依存関係を手動で管理しなければならかったので、非常に辛かった
- この仕組みは、 Maven の後発である Gradle でも利用されている
ローカルリポジトリ
- リモートリポジトリからダウンロードしてきたアーティファクトやメタ情報(pom.xml とか)は、 Maven を実行したマシンのローカルにキャッシュされる
- このキャッシュ先のことを、ローカルリポジトリと呼ぶ
- 毎回ビルドのたびにリモートリポジトリにアクセスしていると、時間がかかるしネットワークが使用できない環境ではビルドもできなくなってしまう
- したがって、 Maven はまず先にローカルリポジトリを検索するようになっている
- ローカルリポジトリに目的のアーティファクトが存在しない場合に、リモートリポジトリに検索に行くようになっている
- リモートリポジトリでアーティファクトが見つかった場合は、それをダウンロードし、ローカルリポジトリに保存する
- これにより、2回目以降はローカルリポジトリを参照すれば良くなるので、ネットワークアクセスなしでもプロジェクトをビルドできる
- ローカルリポジトリの場所は、デフォルトでは
%USERPROFILE%\.m2\repository
になる- Linux 系の OS なら
$HOME/.m2/repository
- Linux 系の OS なら
- ローカルリポジトリにキャッシュされたアーティファクトは、何もしなければ残り続ける
- ディスク容量が足りなくなったなどの理由がない限りは、特に消す必要もない
プライベートリポジトリ
-
Nexus Repository OSS という OSS のアプリケーションを使用すると、独自のリモートリポジトリを構築できる
- Nexus はセントラルリポジトリを管理している Sonatype 社によって開発されている
- 有償版と無償の OSS 版がある
- 例えば、社内だけで共有したいアーティファクトがある場合に、イントラネット内で Nexus サーバーを構築すれば簡単にプライベートリポジトリとして利用できる
- 使用するリモートリポジトリは、 pom.xml で次のように指定できる
- ホスト名やポート番号は実際の環境に合わせて要調整
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<repositories>
<repository>
<id>my-private-repository</id>
<name>My Private Repository</name>
<url>http://private.repository.host/repository</url>
<layout>default</layout>
</repository>
</repositories>
</project>
- さらに、 Nexus は他のリモートリポジトリのプロキシとしての機能も持っている
- 例えば、次のような構成をとっていたとする
- Nexus リポジトリを、セントラルリポジトリのプロキシとして構築する
- Nexus リポジトリをリモートリポジトリとして pom.xml で設定する
- プロジェクトに依存アーティファクトがある場合、 Maven はまずローカルリポジトリを検索する
- ローカルリポジトリにない場合は、次にリモートリポジトリとして指定した Nexus リポジトリを検索する
- Nexus リポジトリにもない場合は、 Nexus がセントラルリポジトリを検索する
- そして、セントラルリポジトリからダウンロードしてきたアーティファクトを Maven に返してくれる
- このとき、 Nexus リポジトリはセントラルリポジトリからダウンロードしたアーティファクトを内部にキャッシュする
- もし同じアーティファクトに対する検索リクエストが来た場合は、キャッシュしたアーティファクトを返すようになる
- これにより、ネットワークトラフィックを抑えたり、何らかの障害でインターネットが繋がらなくなっても、イントラネット内の Nexus リポジトリにさえつながれば依存関係の解決ができるようになる
- 例えば、次のような構成をとっていたとする
プロキシのイメージ
プラグイン(基本)
Maven は、すべての処理が**プラグイン(Plugin)**によって行われる。
例えば、 Java のソースコードをコンパイルするための処理は maven-compiler-plugin によって提供されている。
Maven プロジェクト自身が提供する基本的なプラグインは、Maven – Available Plugins で一覧を確認できる。
プラグインのゴール(Goal)
- プラグインに定義された個々のタスク(処理)のことを**ゴール(Goal)**と呼ぶ
- 使用したいプラグインがどのようなゴールを持つかは、それぞれのプラグインのドキュメントを参照する
- maven-jdeps-plugin に定義されているゴール
- maven-jar-plugin に定義されているゴール
ゴールの実行方法
プラグインのゴールを実行する方法は、大きく次の2つがある。
- コマンドラインで直接指定して実行する
- フェーズに紐付けて実行する
2つ目のフェーズに紐付ける方法は置いておいて、先に1の直接指定する方法について確認する。
完全修飾名指定
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
> mvn org.apache.maven.plugins:maven-jdeps-plugin:3.1.2:jdkinternals
...
classes -> java.base
example -> java.io java.base
example -> java.lang java.base
...
- 何やら長ったらしい引数を mvn コマンドに渡している
- これは、先程の maven-jdeps-plugin の完全修飾名とゴール名を指定している
- 書式にすると
<完全修飾名>:<ゴール名>
といった感じ-
<完全修飾名>
は、ここではorg.apache.maven.plugins:maven-jdeps-plugin:3.1.2
で、 -
<ゴール名>
は、jdkinternals
になる
-
- 完全修飾名指定の場合は、その情報を元にリポジトリからプラグインの jar ファイルが取得され、ゴールが実行される
prefix 指定(プラグインの設定が無い場合)
- 毎回完全修飾名を入力するのは辛いので、以下のような省略した記述もできるようになっている
> mvn jdeps:jdkinternals
...
classes -> java.base
example -> java.io java.base
example -> java.lang java.base
...
- こちらは、
<prefix>:<ゴール名>
という書式の指定方法になる- 完全修飾名指定なのか prefix 指定なのかは、コロン (
:
) の数で区別されている4-
:
が3つなら、groupId:artifactId:version:goal
という完全修飾名での指定 -
:
が2つなら、groupId:artifactId:goal
というバージョン番号抜きでの指定 -
:
が1つなら、prefix:goal
というプレフィックスでの指定
-
- 完全修飾名指定なのか prefix 指定なのかは、コロン (
- prefix 指定の場合、次のような感じで完全修飾名が解決される
- まず、
groupdId
を以下のいずれかとするorg.apache.maven.plugin
org.codehaus.mojo
- 次に、
groupId
ごとにメタデータ (maven-metadata.xml
) を調べる -
maven-metadata.xml
の中身を見ると、artifactId
ごとに prefix の対応が列挙されているのがわかる
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<plugins>
<plugin>
<name>Apache Maven ACR Plugin</name>
<prefix>acr</prefix>
<artifactId>maven-acr-plugin</artifactId>
</plugin>
...
<plugin>
<name>Apache Maven JDeps Plugin</name>
<prefix>jdeps</prefix>
<artifactId>maven-jdeps-plugin</artifactId>
</plugin>
...
</plugins>
</metadata>
- この
maven-metadata.xml
の中からコマンドラインで指定された prefix と一致するものを見つけ、その<artifactId>
をartifactId
とする- コマンドラインで指定した prefix は
jdeps
なので、maven-jdeps-plugin
がartifactId
になる
- コマンドラインで指定した prefix は
- 次は、
artifactId
ごとのメタデータ(maven-metadata.xml
)を調べる
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jdeps-plugin</artifactId>
<versioning>
<latest>3.1.2</latest>
<release>3.1.2</release>
<versions>
<version>3.0.0</version>
<version>3.1.0</version>
<version>3.1.1</version>
<version>3.1.2</version>
</versions>
<lastUpdated>20190619052916</lastUpdated>
</versioning>
</metadata>
- リリースバージョン(
<release>
)に設定されている値をversion
とする- リリースバージョンがなければ、最新バージョン(
<latest>
)5 をversion
とする
- リリースバージョンがなければ、最新バージョン(
- 以上で決定した
groupId
,artifactId
,version
を、完全修飾名とする-
groupId
:org.apache.maven.plugin
-
artifactId
:maven-jdeps-plugin
-
version
:3.1.2
-
プラグインを明示的に設定する
- ↑の方法はメタデータを元にプラグインのバージョンが決まっている
- おそらく新しいバージョンが出たら最新版を使うことになるが、動きを固定できないとビルドが不安定になる恐れがある
- したがって、通常はプロジェクトごとに使用するプラグインのバージョンを固定する
- また、↑の場合 prefix 指定ができるのは
groupId
がorg.apache.maven.plugin
かorg.codehaus.mojo
のいずれかの場合に限られる6- これら以外の
groupId
のプラグインを prefix 指定で利用したい場合は、プラグインを明示的に設定する必要がある
- これら以外の
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
</plugin>
</plugins>
</build>
</project>
- 個々のプラグインは、
<plugin>
タグで設定する-
<plugin>
は、<build><plugins>
の下に列挙する -
<groupId>
,<artifactId>
,<version>
で、プラグインの完全修飾名を指定する
-
- ここでは Asciidoctor の Maven プラグイン を設定している
- この設定により、
asciidoctor-mavne-plugin
のバージョンは1.5.8
に固定されることになる
- この設定により、
> mvn asciidoctor:process-asciidoc
...
[INFO] BUILD SUCCESS
...
prefix 指定の解決(プラグインの指定がある場合)
- ↑のように
<plugin>
でプラグインが明示されている場合、 prefix 指定から完全修飾名を解決する方法が少し変わる - まず Maven は、 pom.xml で明示的に設定されている各プラグインの
plugin.xml
を確認していく- 普通、
plugin.xml
はプラグインの jar 内のMETA-INF/maven
の下に格納されている
- 普通、
- 例えば、
asciidoctor-mavne-plugin
のplugin.xml
は次のようになっている
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated by maven-plugin-tools 3.5 on 2019-03-30 -->
<plugin>
<name>Asciidoctor Maven Plugin and Doxia Parser</name>
<description>Asciidoctor Maven Plugin and Doxia Parser (for Maven Site integration)</description>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<goalPrefix>asciidoctor</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
...
- ここで重要なのは、
<goalPrefix>
に設定されている値になる- この値がコマンドラインで指定された prefix と等しいプラグインの
groupId
とartifactId
が採用される
- この値がコマンドラインで指定された prefix と等しいプラグインの
- 最後に、
version
は次のように決まる- pom.xml で指定されていれば、そのバージョンが採用される
- 指定されていない場合は、
artifactId
ごとのメタデータ(maven-metadata.xml
)から決定される(プラグイン指定なしの場合と同じ方法)
artifactId と prefix の関係
- ここまでの説明で、
artifactId
と prefix には、本質的には関連がないことがわかる-
maven-metadata.xml
の<prefix>
か、plugin.xml
の<goalPrefix>
で prefix が決まっている
-
- しかし、実際に存在するプラグインたちには、
artifactId
と prefix に次のような関係がある- Maven プロジェクトが提供している公式プラグインの場合
-
artifactId
がmaven-XXX-plugin
の場合、XXX
が prefix となる
-
- その他のプラグインの場合
-
artifactId
がXXX-maven-plugin
の場合、XXX
が prefix となる
-
- Maven プロジェクトが提供している公式プラグインの場合
- これらは、そういう命名ルールが推奨されている結果であって、この名前じゃないとプラグインが作れないわけではない
- 作ろうと思えば、この命名ルールとは全く異なる
artifactId
のプラグインを作ることもできる -
ただし、
maven-XXX-plugin
という命名は、それが Maven の公式プラグインであること表すためのものなので、公式以外がこの命名を使うと商標侵害になるので要注意 - 一般的でない命名をしたところで利用者が混乱するだけでメリットがないので、プラグインを自作するなら特別な理由がない限りは
XXX-maven-plugin
というartifactId
にしておくのが無難
- 作ろうと思えば、この命名ルールとは全く異なる
- ということで、
artifactId
と prefix は本質的には関連しないが、実用上は関連しているものと思って問題ない(と思う)- なので、
artifactId
がfoo-maven-plugin
なら、 prefix はfoo
と思っていい(と思う) - 逆に、 prefix が
foo
なら、artifactId
はfoo-maven-plugin
と思っていい(と思う)(Maven プロジェクトが提供しているプラグインでない場合)
- なので、
プラグインの設定(パラメータ)
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>Hello World!!</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn antrun:run
...
main:
[echo] Hello World!!
...
- プラグインの設定(パラメータ)は、
<plugin>
の下の<configuration>
タグで記述できる -
<configuration>
の下に指定できるタグは、プラグインのゴールごとに異なる- ここでは maven-antrun-plugin というプラグインを使用している
- このプラグインには run というゴールがある
-
run
ゴールの説明ページに、<configuration>
で指定可能なパラメータが載っているのがわかる
- 複数のゴールを持つプラグインの場合、
<configuration>
に指定したパラメータはすべてのゴールに対して適用されることになる
プラグインの説明を確認する
- プラグインにどういうゴールがあり、どういうパラメータが指定できるのかは、個々のプラグインの説明ページを見れば確認できる
- しかし、いちいちページを開くのが面倒なときは maven-help-plugin で確認することもできる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn help:describe
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun
This plugin has 2 goals:
antrun:help
Description: Display help information on maven-antrun-plugin.
Call mvn antrun:help -Ddetail=true -Dgoal=<goal-name> to display parameter
details.
antrun:run
Description: Maven AntRun Mojo.
This plugin provides the capability of calling Ant tasks from a POM by
running the nested Ant tasks inside the <tasks/> parameter. It is
encouraged to move the actual tasks to a separate build.xml file and call
that file with an <ant/> task.
For more information, run 'mvn help:describe [...] -Ddetail'
...
-
describe というゴールを実行することで、
<configuration>
で指定したプラグインの説明を確認できる-
<groupId>
と<artifactId>
でプラグインをmaven-antrun-plugin
に特定できるようにしている
-
-
describe
ゴールの説明ページを見るとわかるが、<goal>
でゴールを絞ったり<detail>
で詳細(ゴールごとに指定できるパラメータ)の出力もできる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<goal>run</goal>
<detail>true</detail>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn help:describe
...
antrun:run
Description: Maven AntRun Mojo.
...
Implementation: org.apache.maven.plugin.antrun.AntRunMojo
Language: java
Available parameters:
...
target
The XML for the Ant target. You can add anything you can add between
<target> and </target> in a build.xml.
...
...
- ↑の例は省略しているが、全てのパラメータが説明付きで出力される
システムプロパティで指定する
-
help:describe
の<configuration>
で指定していたパラメータは、システムプロパティから指定することもできる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version>
</plugin>
</plugins>
</build>
</project>
> mvn help:describe -Dplugin=antrun
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun
This plugin has 2 goals:
antrun:help
Description: Display help information on maven-antrun-plugin.
...
antrun:run
Description: Maven AntRun Mojo.
...
For more information, run 'mvn help:describe [...] -Ddetail'
...
- Maven を実行するときのシステムプロパティとして
-Dplugin=antrun
を指定している-
plugin
パラメータの説明は ドキュメント を参照
-
- このように、ゴールのパラメータの中にはシステムプロパティで値を渡すことができるものがある
- すべてのパラメータがシステムプロパティで指定できるわけではない
- あるパラメータがシステムプロパティで指定できるかどうかは、そのパラメータのドキュメントに User property が書かれているかどうかで確認できる
- 例えば、 plugin パラメータのドキュメント では、
User Property: plugin
と書かれているのが確認できる - これは、
plugin
という名前のプロパティで値を指定できることを表している - パラメータ名とプロパティ名は常に一致しているわけではない
-
plugin
はたまたま一致しているだけで、 antrun の skip のように一致していないものもある
-
- 例えば、 plugin パラメータのドキュメント では、
- ここでプロパティと言っているのは、システムプロパティに限らず pom.xml 上の
<properties>
でも指定できることを表している
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<properties>
<plugin>jdeps</plugin>
</properties>
<build>
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-help-plugin</artifactId>
<version>3.2.0</version>
</plugin>
</plugins>
</build>
</project>
-
<properties>
で、plugin
にjdeps
を設定している
> mvn help:describe
...
Name: Apache Maven JDeps Plugin
Description: The JDeps Plugin uses the jdeps tool to analyze classes for
internal API calls.
Group Id: org.apache.maven.plugins
Artifact Id: maven-jdeps-plugin
Version: 3.1.2
Goal Prefix: jdeps
...
> mvn help:describe -Dplugin=antrun
...
Name: Apache Maven AntRun Plugin
Description: Runs Ant scripts embedded in the POM
Group Id: org.apache.maven.plugins
Artifact Id: maven-antrun-plugin
Version: 1.8
Goal Prefix: antrun
...
- システムプロパティを指定していない場合は、
<properties>
で指定した値(jdeps
)が採用された - システムプロパティを指定した場合は、そちらの値(
antrun
)が採用された
ライフサイクルとフェーズ
- 「ビルド」と一口に言っても、その内容は様々な処理を含んでいる
- 例えば、簡単な Java プログラムをビルドするには、次のような処理が考えられる
- ソースコードをコンパイルする
- リソースファイルを収集する
- テストコードをコンパイルする
- テスト用のリソースファイルを収集する
- テストコードを実行する
- コンパイル結果とリソースファイルをまとめて、 jar などのアーカイブにパッケージングする
- アーカイブを所定の場所に格納する
- Maven では、これら1つ1つの処理を**フェーズ(Phase)**と呼ぶ
- そして、一連のフェーズのセットを**ライフサイクル(Lifecycle)**と呼ぶ
組み込みのライフサイクル
- Maven には、標準で次の3つのライフサイクルが用意されている
default
clean
site
default ライフサイクル
-
default
ライフサイクルは、プロジェクトをビルドしてデプロイするまでのライフサイクルを定義している -
default
ライフサイクルは、次のフェーズで構成されているvalidate
initialize
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
process-test-classes
test
prepare-package
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
clean ライフサイクル
-
clean
ライフサイクルは、プロジェクトの成果物を削除するライフサイクルを定義している -
clean
ライフサイクルは、次のフェーズで構成されているpre-clean
clean
post-clean
site ライフサイクル
-
site
ライフサイクルは、プロジェクトの Web サイト生成のライフサイクルを定義している -
site
ライフサイクルは、次のフェーズで構成されているpre-site
site
post-site
site-deploy
フェーズとプラグインのゴールの関係
- 各ライフサイクルのフェーズには、そのフェーズで実行するプラグインのゴールが紐付けられている
clean, site ライフサイクルの場合
- 例えば、
clean
ライフサイクルとsite
ライフサイクルでは、次のようにゴールが紐付けられている
clean プラグイン
フェーズ | プラグイン | ゴール |
---|---|---|
pre-clean |
- | - |
clean |
maven-clean-plugin |
clean |
post-clean |
- | - |
site プラグイン
フェーズ | プラグイン | ゴール |
---|---|---|
pre-site |
- | - |
site |
maven-site-plugin |
site |
post-site |
- | - |
site-deploy |
maven-site-plugin |
deploy |
- つまり、
clean
フェーズを実行すると、maven-clean-plugin
のclean
ゴールが実行されることになる
default ライフサイクルの場合
-
default
ライフサイクルの場合、フェーズとゴールの紐付けは固定ではない -
default
ライフサイクルで実行されるゴールは、そのプロジェクトの packaging によって変化する
packaging
- packaging とは、そのプロジェクトをどのようにパッケージ化するかを決める設定値で、次のいずれかの値が指定できる
pom
jar
ejb
maven-plugin
war
ear
rar
- この packaging は、 pom.xml 上で次のように指定する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<packaging>jar</packaging>
...
</project>
- ここでは、 packaging として
jar
を指定している - なお、
<packaging>
タグは pom.xml で省略することができる- 省略した場合、デフォルトは
jar
になる
- 省略した場合、デフォルトは
packaging ごとのゴール
-
default
ライフサイクルのフェーズに紐付けられているゴールは、 packaging によって次のように異なる - 一例として、
pom
とjar
に紐付けられたゴールは次のようになっている- フェーズの数が多いので、何も紐付けられていないフェーズは除外している(
validate
など) - これら以外の packaging については Maven Core – Plugin Bindings for Default Lifecycle Reference などを参照
- フェーズの数が多いので、何も紐付けられていないフェーズは除外している(
pom
フェーズ | プラグイン | ゴール |
---|---|---|
install |
maven-install-plugin |
install |
deploy |
maven-deploy-plugin |
deploy |
jar
フェーズ | プラグイン | ゴール |
---|---|---|
process-resources |
maven-resources-plugin |
resources |
compile |
maven-compiler-plugin |
compile |
process-test-resources |
maven-resources-plugin |
testResources |
test-compile |
maven-compiler-plugin |
testCompile |
test |
maven-surefire-plugin |
test |
package |
maven-jar-plugin |
jar |
install |
maven-install-plugin |
install |
deploy |
maven-deploy-plugin |
deploy |
フェーズを実行する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
> mvn test
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
- フェーズは、 mvn コマンドの引数に指定することで実行できる
- ここでは、 packaging が
jar
のプロジェクトでtest
フェーズを実行してみている-
<packaing>
の記述が省略されているので、 packaing はデフォルトのjar
になる
-
- フェーズを指定して実行した場合、そのフェーズより前のフェーズもすべて順番に実行される
- 例えば、
test
フェーズの前にはvalidate
やprocess-resources
,test-compile
といったフェーズが定義されている - 上の例では
maven-resources-plugin:resources
,maven-compiler-plugin:compile
, ... と実行されていっていることがわかる
- 例えば、
- あるフェーズが実行されると、そのフェーズに紐付いているゴールがすべて実行される
-
maven-resources-plugin:resources
ゴールはresources
フェーズに、
maven-compiler-plugin:compile
ゴールはcompile
フェーズに紐付けらている
-
フェーズやゴールを複数指定して実行する
> mvn clean compiler:compile
- フェーズやゴールは、 mvn コマンドに複数指定して実行できる
- この場合の実行順序は、
clean
→compiler:compile
の順序になる - つまり、引数で指定した順番で実行されるようになっている
フェーズにゴールを紐付ける
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<target>
<echo>Hello Antrun!!</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (default) @ hello ---
[INFO] Executing tasks
main:
[echo] Hello Antrun!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
- 任意のフェーズに対して、任意のゴールを紐付けることができる
- フェーズとゴールの紐付けは、プラグインの設定の中で
<execution>
で行う -
<phase>
でフェーズを指定し、<goals><goal>
で紐付けるゴールを指定する - ここでは、
validate
フェーズに対してmaven-antrun-plugin
のrun
ゴールを紐付けている -
<goals><goal>
という構造からも分かる通り、1つのフェーズに対して同じプラグインの複数のゴールを紐付けることもできる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jdeps-plugin</artifactId>
<version>3.2.6</version>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>jdkinternals</goal>
<goal>test-jdkinternals</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
> mvn verify
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] --- maven-jdeps-plugin:3.1.2:jdkinternals (default) @ hello ---
...
[INFO] --- maven-jdeps-plugin:3.1.2:test-jdkinternals (default) @ hello ---
-
maven-jdeps-plugin
のjdkinternals
とtest-jdkinternals
をverify
フェーズに紐づけている - 1つのフェーズに複数のゴールが紐付けられている場合、それらのゴールは pom.xml 上で宣言されている順番で実行される7
- つまり、↑の設定の場合は
jdkinternals
→test-jdkinternals
の順番で実行される -
<goal>
タグの順序を変えれば、実行順序を変更できる
- つまり、↑の設定の場合は
複数のフェーズに紐付ける
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>foo</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
<execution>
<id>bar</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
<configuration>
<target>
<echo>Hello Antrun!!</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (foo) @ hello ---
...
[echo] Hello Antrun!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-antrun-plugin:1.8:run (bar) @ hello ---
...
[echo] Hello Antrun!!
...
-
<execution>
タグを複数書けば、複数のフェーズに対してゴールの紐付けができるようになる -
<execution>
を複数指定する場合は、<id>
タグも指定しなければならない-
<id>
には、その<execution>
を識別できる一意な値を設定する - この値は、実行時のコンソールに出力されている(
maven-antrun-plugin:1.8:run (foo)
の(foo)
の部分) - どの
<execution>
が実行されたのかを識別するための情報になるので、ビルドがうまく行かないときなどのデバッグに役立つ - したがって、識別しやすいわかりやすい名前を付けておくのがいい
-
execution ごとに設定する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>foo</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>VALIDATE!!</echo>
</target>
</configuration>
</execution>
<execution>
<id>bar</id>
<phase>compile</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>COMPILE!!</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
> mvn compile
...
[INFO] --- maven-antrun-plugin:1.8:run (foo) @ hello ---
...
[echo] VALIDATE!!
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-antrun-plugin:1.8:run (bar) @ hello ---
...
[echo] COMPILE!!
...
- ゴールの設定を記述する
<configuration>
は、<execution>
ごとにも指定できる - これにより、特定のフェーズごとに設定を分けるといったことが可能になる
ゴールを直接実行したときに採用される特別なid
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<target>
<echo>Hello @ default-cli</echo>
</target>
</configuration>
</execution>
<execution>
<id>validate-phase</id>
<phase>validate</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<echo>Hello @ validate-phase</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
> mvn antrun:run
...
[echo] Hello @ default-cli
...
> mvn validate
...
[echo] Hello @ validate-phase
...
- ゴールを直接実行した場合、
id
はデフォルトでdefault-cli
になる - したがって、この値で
<execution><id>
を設定しておけば、ゴールを直接実行したときにだけ適用される設定を記述できる
id を指定して実行する
- Maven の 3.3.1 からは、
<execution>
の<id>
を指定してゴールを実行することができるようになっている
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<target>
<echo>Hello default-cli!!</echo>
</target>
</configuration>
</execution>
<execution>
<id>foo</id>
<configuration>
<target>
<echo>Hello Foo!!</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
-
default-cli
とfoo
の、2つの<id>
で<execution>
を定義している
> mvn antrun:run
...
[echo] Hello default-cli!!
> mvn antrun:run@foo
...
[echo] Hello Foo!!
-
antrun:run
だけで実行した場合は、default-cli
のほうが実行される -
antrun:run@foo
のように、ゴール指定の後ろに@<id>
と続けることで、指定された<id>
の<execution>
が実行される
デフォルトのフェーズ
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
> mvn verify
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] --- maven-checkstyle-plugin:3.1.1:check (default) @ hello ---
-
maven-checkstyle-plugin を追加して
verify
フェーズを実行している - pom.xml では、
<execution>
で<goal>check</goal>
しか指定しておらず、<phase>
の指定はしていない - しかし、実行結果を見ると
verify
フェーズでcheck
ゴールが実行されていることがわかる - ゴールは、デフォルトで紐づくフェーズを定義できるようになっている
-
check ゴールの説明ページ を見ると、デフォルトフェーズが
verify
と書かれているのがわかる
-
check ゴールの説明ページ を見ると、デフォルトフェーズが
- もし
<execution>
でフェーズが明示されていない場合は、このデフォルトで紐付いているフェーズで実行されるようになっている - あるゴールにフェーズが1つも紐付いていない場合、そのゴールはコマンドラインで直接指定しなければ起動できない
プラグインのバージョン指定
- プラグインは、
<plugin>
で明示的に設定しなくてもある程度使用できる- packaging の設定によって、いくつかの基本的なプラグイン(
maven-compiler-plugin
など)は自動的に適用される - プレフィックス指定された場合は、
groupId
,artifactId
,version
が自動解決される
- packaging の設定によって、いくつかの基本的なプラグイン(
- 設定がなくても動くのであれば、わざわざ
<plugin>
を書かなくても良いような気がする - しかし、
<plugin>
の設定無しで実行する方法は、プラグインのバージョンが実行のたびに変わる可能性がある - 例えば、 packaging によって自動設定されるプラグインは、 Maven のバージョンによって変化する可能性がある
- たとえば、 3.6.3 で設定されるプラグインのバージョンはここで確認できる
- 開発者の使っている Maven のバージョンが揃っていないと、開発者ごとにビルド結果が変わるかもしれない
- (そもそも、デフォルトで設定されるプラグインのバージョンは古い)
- また、プレフィックス指定で解決されるプラグインのバージョンは、基本的にリポジトリの最新になる
- これも、実行時の最新が変われば結果も変化する恐れがある
- したがって、使用するプラグインのバージョンは、面倒だと思っても基本的に明示するのが良い
- ただし、ここに記載している pom.xml の例は、以下の理由によりプラグインの設定を省略していることがある
- 記述量が多くなると、それだけで読む気が失せる
- 必要な部分(今説明しようとしている部分)に注目できない
プロジェクトの親子関係
- プロジェクトには親子関係をもたせることができる
|-pom.xml
`-child/
`-pom.xml
- トップフォルダに親プロジェクトの pom.xml を置き、
child
フォルダの下に子プロジェクトの pom.xml を置いている
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>artifactId = ${project.artifactId}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 親プロジェクトの pom.xml では、
<packaging>
にpom
を指定しなければならない
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child</artifactId>
</project>
- 子プロジェクトの pomx.ml では、親を指定するために
<parent>
を使用する -
<parent>
では、親POM を特定するためにgroupId
,artifactId
,version
を指定する - 親子関係のあるプロジェクトでは、子の POM は親の POM を継承する
- このため、子の pom.xml に記述されていない設定は、親の pom.xml の設定が引き継がれることになる
-
<groupId>
や<version>
なども親の設定が引き継がれるため、子の pom.xml では記述が不要になる - ただし、
<artifactId>
はさすがに指定しなければならない
-
> mvn antrun:run
...
[echo] artifactId = parent
> mvn antrun:run
...
[echo] artifactId = child
-
<plugins>
の設定も引き継がれているため、親プロジェクトで設定されたantrun
プラグインを子プロジェクトでも使用できている
親プロジェクトの解決方法
-
<parent>
で設定された親の pom.xml は、次の順序で検索される-
<relativePath>
が指定されている場合は、その場所の pom.xml を参照 -
<relativePath>
が指定されていない場合は、1つ上の階層の pom.xml を参照 - それも無ければ、リポジトリ(ローカル or リモート)を検索して参照
-
- ↑の例は親プロジェクトが子プロジェクトの1つ上だったため、特に場所の指定をしなくても親 POM が見つかっていた
親プロジェクトの場所を明示する
- しかし、↓のようなフォルダ構成の場合は、
<relativePath>
によるパスの指定が必要になる
|-parent/
| `-pom.xml
`-child/
`-pom.xml
-
parent
とchild
が横並びになっており、子プロジェクトの1つ上に親プロジェクトの pom.xml が存在しない状態になっている - この状態で子プロジェクトで Maven を実行すると、次のようなエラーになる
> mvn antrun:run
...
[FATAL] Non-resolvable parent POM for example:child:1.0.0: Failure to find example:parent:pom:1.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced and 'parent.relativePath' points at wrong local POM @ line 6, column 11
@
...
- これを解決するには、リポジトリに親プロジェクトの jar を登録するか、以下のように
<relativePath>
で親プロジェクトの場所を明示してあげる必要がある
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>child</artifactId>
</project>
> mvn antrun:run
...
[echo] artifactId = child
- 無事親プロジェクトが見つかって動作した
POMのマージ
- POMに親子関係がある場合、子のPOMには親のPOMがマージされる
- マージがどのように行われるのか確認してみる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<hoge>PARENT</hoge>
<fuga>PARENT</fuga>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>parent/dir</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>hoge = ${hoge}</echo>
<echo>fuga = ${fuga}</echo>
<echo>piyo = ${piyo}</echo>
<echo>dependencies[0].artifactId = ${project.dependencies[0].artifactId}</echo>
<echo>dependencies[1].artifactId = ${project.dependencies[1].artifactId}</echo>
<echo>resources[0].directory = ${project.build.resources[0].directory}</echo>
<echo>resources[1].directory = ${project.build.resources[1].directory}</echo>
<echo>plugins[0] = ${project.build.plugins[0].id}</echo>
<echo>plugins[1] = ${project.build.plugins[1].id}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 検証のために、いくつかの設定を記述し、
antrun
プラグインで設定内容を表示できるようにしている
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child</artifactId>
<properties>
<fuga>CHILD</fuga>
<piyo>CHILD</piyo>
</properties>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.14</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>child/dir</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jdeps-plugin</artifactId>
<version>3.1.2</version>
</plugin>
</plugins>
</build>
</project>
- 親POM を継承し、いくつかの設定が重なるように記述している
> mvn antrun:run
...
[echo] hoge = PARENT
[echo] fuga = PARENT
[echo] piyo = ${piyo}
[echo] dependencies[0].artifactId = commons-lang3
[echo] dependencies[1].artifactId = ${project.dependencies[1].artifactId}
[echo] resources[0].directory = parent/dir
[echo] resources[1].directory = ${project.build.resources[1].directory}
[echo] plugins[0] = org.apache.maven.plugins:maven-antrun-plugin:1.8
[echo] plugins[1] = ${project.build.plugins[1].id}
- まずは、親プロジェクトで各設定値を出力してみる
- 当然親POM で設定した値だけが出力され、設定されていない値は式が評価できずにそのまま出力されている
> mvn antrun:run
...
[echo] hoge = PARENT
[echo] fuga = CHILD
[echo] piyo = CHILD
[echo] dependencies[0].artifactId = commons-codec
[echo] dependencies[1].artifactId = commons-lang3
[echo] resources[0].directory = child/dir
[echo] resources[1].directory = ${project.build.resources[1].directory}
[echo] plugins[0] = org.apache.maven.plugins:maven-antrun-plugin:1.8
[echo] plugins[1] = org.apache.maven.plugins:maven-jdeps-plugin:3.1.2
- 子プロジェクトで出力した結果、単一値の設定で重なる部分(
<properties><fuga>
など)は子POM の設定が採用されている- 一方、複数値の設定で重なる部分(
<dependencies>
や<plugins>
など)は、親POM をベースにして子POM の要素が追加されている
- 一方、複数値の設定で重なる部分(
- 基本的に、単一値の項目は上書きされ、複数値の項目 (
<plugins>
など) は要素が追加される- ただし一部例外があり、例えば
<build><resources>
は複数値項目だが上書きされている -
公式ドキュメントでは、
<resources>
もマージの対象と書かれているけど実際は上書きされる - 実装的には ModelMerger というクラスで POM のマージが行われているっぽい
- このクラスの mergeBuildBase_Resources() で
<resources>
のマージが行われる - この実装では、
<resource>
は追加する形でマージされることになっている
- このクラスの mergeBuildBase_Resources() で
- しかし、実際にはこの
ModelMerger
のサブクラスである MavenModelMerger でマージが行われているっぽい-
MavenModelMerger
は mergeBuildBase_Resources() をオーバーライドしていて、target
(子POM)の<resources>
が空の場合のみ親POMの内容をマージするようにしている - つまり、子POMに
<resources>
が存在すればマージは行われず、子POMの内容だけが採用される(結果として上書きの動作になる)
-
-
<resources>
以外は、このMavenModelMerger
の実装を見てみるしかない?
- ただし一部例外があり、例えば
マージ後の POM を確認する
- POM には親子関係があり、複数の POM を親に持つような場合は末端の POM を見ただけでは最終的な状態がわからない
- 特にビルドがうまく動作しないときは、 POM が期待通りにマージされているか確認したくなることがよくある
- そういうときは、
maven-help-plugin
の effective-pom ゴールが利用できる
> mvn help:effective-pom
...
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<groupId>example</groupId>
<artifactId>child</artifactId>
<version>1.0.0</version>
(長いので省略)
...
-
effective-pom
ゴールを実行すると、Super POM を含めてすべての親POM の情報がマージされた最終的な POM が出力される - これを見ることで、そのプロジェクトの設定が実際どうなっているのかを確認できる
configurations のマージ
- プラグインの設定を記述する
<configurations>
以下は、プラグインごとに設定が異なるため、固定のスキーマ定義を持っていない - したがって、
<configurations>
のマージは一定のルールに沿って画一的に行われる- 例えば、
<execution>
なら<id>
で識別ができるように定義が固まっているので、同じ<id>
をマージするといったことができる - しかし、
<configurations>
の下のタグはそういう識別ができないので、だいたい同じ位置のタグをそれっぽくマージする感じになる- (正確な仕様や実装を確認したわけではないので、そういう雰囲気というくらいの気持ちで)
- 例えば、
- どういうふうにマージされるのか、実際の例で見てみる
...
<configuration>
<persons>
<person>
<name>Taro</name>
<age>18</age>
</person>
<person>
<name>Hanako</name>
<sex>female</sex>
</person>
</persons>
</configuration>
...
...
<configuration>
<persons>
<person>
<sex>male</sex>
</person>
<cat>nya-</cat>
<person>
<name>Ayako</name>
<age>15</age>
</person>
<person>
<name>Rin</name>
<age>12</age>
<sex>female</sex>
</person>
</persons>
</configuration>
...
この状態で help:effective-pom
でマージ結果を確認する。
...
<configuration>
<persons>
<person>
<sex>male</sex>
<name>Taro</name>
<age>18</age>
</person>
<cat>nya-</cat>
<person>
<name>Ayako</name>
<age>15</age>
<sex>female</sex>
</person>
<person>
<name>Rin</name>
<age>12</age>
<sex>female</sex>
</person>
</persons>
</configuration>
...
- 言葉で説明するのは難しいが、なんかいい感じにマージされているのが分かると思う
- おそらく、同じ位置に同名のタグがあれば、その中身を再帰的にマージしていっている感じだと思う
マージの仕方を制御する
- デフォルトの動作でも、そこそこよしなにマージしてくれる気がする
- しかし、それだと色々問題があるという場合は、デフォルトのマージの挙動を変更することもできる
...
<configuration>
<persons>
<person>
<name>Taro</name>
<age>18</age>
</person>
<person>
<name>Hanako</name>
<sex>female</sex>
</person>
</persons>
</configuration>
...
- 親POMは変更なし
...
<configuration>
<persons>
<person combine.self="override">
<sex>male</sex>
</person>
<cat>nya-</cat>
<person combine.children="append">
<name>Ayako</name>
<age>15</age>
</person>
<person>
<name>Rin</name>
<age>12</age>
<sex>female</sex>
</person>
</persons>
</configuration>
...
-
combile.self="override"
とcombine.children="append"
という属性を追加している
...
<configuration>
<persons>
<person combine.self="override">
<sex>male</sex>
</person>
<cat>nya-</cat>
<person combine.children="append">
<name>Hanako</name>
<sex>female</sex>
<name>Ayako</name>
<age>15</age>
</person>
<person>
<name>Rin</name>
<age>12</age>
<sex>female</sex>
</person>
</persons>
</configuration>
...
-
combine.*
の属性を追加した要素だけ、マージのされかたが変わっているのが分かる -
combine.self="override"
を指定した場合、親POMの要素は完全に捨てられて子POMの要素だけが使用される -
combine.children="append"
を指定した場合、親POMの要素の末尾に子POMの要素を単純に追加する形になる - これら2つの属性を子POMで使用することで、マージの仕方をある程度調整できるようになる
- ただし、これらの属性は、この属性を記述した要素にだけ効果がある
- 入れ子の要素にまで伝播することはない
- 入れ子の要素で動きを変更したい場合は、別途入れ子の要素でも同じように
combine.*
属性を指定する必要がある
親プロジェクトで定義だけをまとめる
- 親プロジェクトの設定は、常に子プロジェクトに継承される
- すべての子プロジェクトで共通な設定の場合は便利だが、一部の子プロジェクトでだけ必要な設定の場合は、余計な継承が発生してしまう
- そこで、定義だけを親プロジェクトにまとめて、実際の適用はその定義を使用したい子プロジェクトで明示的に行う方法が用意されている
プラグインの定義をまとめる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>validate</phase>
<goals><goal>run</goal></goals>
<configuration>
<target>
<echo>Hello ${project.artifactId}</echo>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- 親プロジェクトでプラグインの定義をまとめるには、
<pluginManagement>
を使用する - この下は、通常のプラグイン定義と同じように
<plugins>
を記述できる - ただし、ここで記述したものはあくまで設定を定義しただけで、プロジェクトには適用されていない
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child1</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 実際にプロジェクトに適用するには、
<groupId>
,<artifactId>
を適用したいプロジェクトのプラグイン定義に記述する- バージョンを省略した場合は、
<pluginManagement>
に記述したものが使用される
- バージョンを省略した場合は、
- 子プロジェクトで個別の設定を追加することも可能
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child2</artifactId>
</project>
- こちらは、特に何もプラグインを適用していない
> mvn validate
...
[INFO] ---------------------------< example:child1 >---------------------------
[INFO] Building child1 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-antrun-plugin:1.8:run (default) @ child1 ---
[INFO] Executing tasks
main:
[echo] Hello child1
[INFO] Executed tasks
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
- 親POMで定義された設定で、プラグインが適用されている
> mvn validate
...
[INFO] ---------------------------< example:child2 >---------------------------
[INFO] Building child2 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
- プラグインは何も適用されていない
依存関係の定義をまとめる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
</project>
- 依存関係の定義を親にまとめるには、
<dependencyManagement>
を使用する - この下は、通常の
<dependencies>
同様の記述が可能 -
<pluginManagement>
同様、ここでの定義は宣言のみで、プロジェクトへの適用は個別に行う
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child1</artifactId>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
</dependencies>
</project>
- 子プロジェクトでの適用は、依存関係に
<groupId>
と<artifactId>
を記述して行う -
<version>
を省略した場合は、<dependencyManagement>
で指定したものが使用される- 子プロジェクトで独自に
<version>
を上書き指定することも可能
- 子プロジェクトで独自に
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child2</artifactId>
</project>
- こちらは、特に依存関係を何も定義していない
> mvn help:effective-pom
...
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
...
- 親POMで設定したバージョンで
commons-io
が適用されている
> mvn help:effective-pom
...
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
<scope>compile</scope>
</dependency>
</dependencies>
...
-
commons-io
の依存関係は適用されていない
プロジェクトの集約(Aggregation)
- 親子関係は子が親を参照する形だった
- 一方で、プロジェクトの集約では逆に親が子を参照する形になる
- プロジェクトを集約すると、親プロジェクトでまとめてゴールやフェーズを指定して実行できるようになる
|-pom.xml
`-sub/
`-pom.xml
- トップフォルダが集約元となるプロジェクト
-
child
フォルダの下に、集約対象となるプロジェクトが存在する
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>root</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>sub</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>Hello ${project.artifactId}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- プロジェクトを集約するには、集約元となるプロジェクトで
<modules>
を使用する -
<modules>
の下に、集約対象となるプロジェクトを<module>
で列挙していく - 集約元となるプロジェクトの
<packaging>
は、pom
にしておく必要がある
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>sub</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>Hello ${project.artifactId}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 集約対象の pom.xml は普通に書く
- この POM は集約元のプロジェクトを継承しているわけではないので、
<groupId>
の記述などは個別にしている
> mvn antrun:run
...
main:
[echo] Hello sub
...
main:
[echo] Hello root
...
- 集約元プロジェクトでプラグインのゴールを実行すると、集約対象のプロジェクトでも同じゴールが実行されている
- このように、プロジェクトを集約すると、集約元プロジェクトで実行したコマンドが集約先のプロジェクトでも実行されるようになる
- 全プロジェクトをまとめてビルドしたいときなどは、この仕組があると便利
集約と親子関係を組み合わせる
- 集約と親子関係の仕組みは、組み合わせることができる(というか、普通は組み合わせて使用する)
|-pom.xml
`-child/
`-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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>child</module>
</modules>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>Hello ${project.artifactId}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- 親POMの構成は、とくに変化無し
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>example</groupId>
<artifactId>parent</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>child</artifactId>
</project>
-
<parent>
で親プロジェクトを宣言 - これにより、プラグインの設定は親の設定を継承できるようになり、記述が不要になった
> mvn antrun:run
...
main:
[echo] Hello parent
...
main:
[echo] Hello child
...
- 親子関係と集約を同時に適用できている
Java プロジェクトのビルド
- packaging が
jar
のプロジェクトがどのようにビルドされるのかを読み解いていく。
default ライフサイクルで実行されるゴール
- 普通に Java をビルドするプロジェクトを作る場合、 packaging はデフォルトの
jar
になる - したがって、
default
ライフサイクルで実行されるゴールは以下になるresources:resources
compiler:compile
resources:testResources
compiler:testCompile
surefire:test
jar:jar
install:install
deploy:deploy
- 各ゴールが何をしているのか、1つずつ見ていく
リソースを収集する
- 最初に実行されるのは、
maven-resources-plugin
の resources ゴール となっている
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
- 最小構成の pom.xml で検証する
|-pom.xml
`-src/main/
|-java/
| `example/
| `-App.java
`-resources/
|-hoge.txt
`-foo/
`-bar.txt
-
src/main/resources
以下に適当にファイルやフォルダを配置している
> mvn resources:resources
...
[INFO] --- maven-resources-plugin:2.6:resources (default-cli) @ hello ---
...
[INFO] BUILD SUCCESS
-
resources:resources
ゴールを直接実行
|-pom.xml
|-src/main/
| |-java/
| | `example/
| | `-App.java
| `-resources/
| |-hoge.txt
| `-foo/
| `-bar.txt
`-target/classes/
|-hoge.txt
`-foo/
`-bar.txt
-
target/classes
フォルダの下に、src/main/resources
以下の内容がコピーされている -
maven-resources-plugin
は、プロジェクトのリソースフォルダを出力先フォルダにコピーする処理を提供する - リソースフォルダと出力先フォルダは、デフォルトでは次のようになっている
- リソースフォルダ:
src/main/resources
- 出力先フォルダ:
target/classes
- リソースフォルダ:
- リソースフォルダは、
${project.build.resources}
に設定されているフォルダが対象となる- この値は、 Super POM で
${project.basedir}/src/main/resources
が設定されている
- この値は、 Super POM で
- 出力先のフォルダは、
resources
ゴールのoutputDirectory
パラメータで指定された場所となっている- このパラメータには、デフォルトで
${project.build.outputDirectory}
が設定されている - そして、この値は Super POM によってデフォルトで
${project.build.directory}/classes
が設定されている - さらに、
${project.build.directory}
には${project.basedir}/target
が設定されている
- このパラメータには、デフォルトで
- ということで、例えば次のように 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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<resources>
<resource>
<directory>src/main/hoge</directory>
</resource>
</resources>
</build>
</project>
- リソースフォルダを
src/main/hoge
に変更している
|-pom.xml
`-src/main/
|-hoge/
| |-fizz.txt
| `-hoge/
| `-fuga.txt
|
`-resources/
|-buzz.txt
`-foo/
`-bar.txt
-
src/main/resources
とsrc/main/hoge
の2つを用意している
> mvn resources:resources
...
|-pom.xml
|-src/main/
| |-hoge/
| | |-fizz.txt
| | `-hoge/
| | `-fuga.txt
| |
| `-resources/
| |-buzz.txt
| `-foo/
| `-bar.txt
|
`-target/classes/
|-fizz.txt
`-hoge/
`-fuga.txt
-
src/main/hoge
以下だけがコピーされるように変わっている
- 整理すると
resources:resources
ゴールは、デフォルトで次のように動作する-
${project.basedir}/src/main/resources
以下のファイルやフォルダを、${project.basedir}/target/classes
の下にコピーする - リソースフォルダは、
<project><build><resources>
で設定できる - 出力先フォルダは、
<project><build><outputDirectory>
で設定できる
-
Java ソースをコンパイルする
- 次に実行されるゴールは、 maven-compiler-plugin の compile ゴールとなる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
- 最小構成の POM で検証する
|-pom.xml
`-src/main/java
`-example/
`-Hello.java
-
Hello.java
だけを配置したシンプルな構成
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ hello ---
...
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] ソース・オプション5は現在サポートされていません。6以降を使用してください。
[ERROR] ターゲット・オプション1.5は現在サポートされていません。1.6以降を使用してください。
- エラーで落ちた
- コンパイル時のオプションである -source および -target に指定されているバージョンが
5
,1.5
で古すぎるというエラー内容になっている - JDK 11 では、
-source
,-target
に 5 以下のバージョンは指定できなくなっている - packaging が
jar
の場合、デフォルトで適用されるmaven-compiler-plugin
のバージョンは 3.1 となっている -
maven-compiler-plugin
の 3.1 では、-source
および-target
のバージョン指定は、デフォルトで 1.5 となっている- ちなみに、確認時最新の
maven-compiler-plugin
の 3.8.1 では、 1.6 がデフォルトとなっている
- ちなみに、確認時最新の
- ということで、このエラーを解消するには、
maven-compiler-plugin
のバージョンを上げるか、source
,target
の指定を上げる必要がある - 動作を確定させるためにも、両方とも指定しておくのが無難
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
-
maven-compiler-plugin
でsource
を指定するには、 source パラメータ を設定する-
<configuration>
で指定することもできるが、maven.compiler.source
プロパティでも設定できるようになっている - ここでは、プロパティを使った設定を使用している(こっちの方が一般的?)
-
-
target
も同様で、maven.compiler.target
プロパティで設定できるようになっている
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ hello ---
...
[WARNING] File encoding has not been set, using platform encoding MS932, i.e. build is platform dependent!
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...
- コンパイルはできたが、なにやら警告が出ている
- 警告内容は、ソースファイルのエンコーディングが環境デフォルトに依存している、というもの
- ソースファイルのエンコーディングの指定は、 encoding パラメータ で行う
- 説明に書いているように、デフォルトでは
${project.build.sourceEncoding}
を使用するようになっている - しかし、この値は Super POM でも設定されていないので、結果的に環境デフォルト(Windows なら MS932 など)となっている
- 説明に書いているように、デフォルトでは
- ということで、エンコーディングも設定してみる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
-
${project.build.sourceEncoding}
という記述から、<project><build>
の下に<sourceEncoding>
という要素を記述するのかなというイメージが湧くが、実際はプロパティを宣言する(誰が分かるねん)- ちなみに、
encoding
というプロパティでも設定可能となっているが、ネットで調べるとだいたいproject.build.sourceEncoding
の方法が引っかかる -
encoding
だと、 Java ソース以外のエンコーディングにも適用される可能性があるとか、そういうのがあるのかな?(適当)
- ちなみに、
> mvn compiler:compile
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-cli) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
- 無事、警告もなくコンパイルが成功した
|-pom.xml
|-src/main/java/
| :
`-target/
|-classes/
: `-example/
`-Hello.class
- コンパイル結果は、
target/classes
の下に出力されている- 出力先の設定は、 compile ゴール のドキュメント上には記載されていない
- これは、
compile
ゴールの実装クラスである outputDirectory フィールドで宣言されている - デフォルト値は
${project.build.outputDirectory}
となっている -
readonly=true
が設定されているため、このフィールドを外部から直接設定することはできないようになっている
- ソースフォルダは、
src/main/java
がデフォルトで使用される- こちらも、ドキュメント上は記載がない
- 実装上は、 compileSourceRoots というフィールドで宣言されている
-
defaultValue
を見ると、${project.compileSourceRoots}
が設定されていることがわかる - しかし、この
project.compileSourceRoots
というプロパティを pom.xml 上で参照しようとしても、値を確認することはできない -
compileSourceRoots
は MavenProject クラスのフィールドとして宣言されている - このフィールドは、 DefaultProjectBuilder によって値が設定されている
- この実装から、
project.build.sourceDirectory
がcompileSourceRoots
に設定されていることが分かる - そして、
project.build.sourceDirectory
には、 Super POM によって${project.basedir}/src/main/java
が設定されている - この仕組みを見ると、ソースフォルダを複数設定できないことが予想できる
- 複数のソースフォルダを設定したい場合は、 build-helper-maven-plugin というプラグインを導入する必要がある
- 整理すると、
compiler:compile
では次のことが行われる-
${project.basedir}/src/main/java
の下にある Java ソースコードをコンパイルして、${project.basedir}/target/classes
に出力する - javac の
-source
,-target
オプションは、maven.compiler.source
,maven.compiler.target
プロパティで指定できる - エンコーディングの指定は、
project.build.sourceEncoding
プロパティで指定できる - ソースフォルダは
<project><build><sourceDirectory>
で指定できる- 複数のフォルダを指定したい場合は、
build-helper-maven-plugin
が必要
- 複数のフォルダを指定したい場合は、
- 出力先フォルダは、
<project><build><outputDirectory>
-
テスト用のリソース収集とコンパイル
- 次に実行されるのは、テスト用のリソースを収集する resources:testResources と、
テスト用のソースコードをコンパイルする compiler:testCompile になる - それぞれの基本的な動作は
resources:resources
,compiler:compile
と同じなので、ざっくりとだけ
|-pom.xml
`-src/test/
|-java/
| `-example/
| `-HelloTest.java
|
`-resources/
|-foo/
| `-bar.txt
`-fizz.txt
-
src/test/java
の下にテスト用のソースコードを、
src/test/resources
の下にテスト用のリソースファイルを配置する
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
- pom.xml は、
compiler:compile
のときと同じ
> mvn resources:testResources compiler:testCompile
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-cli) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-cli) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
|-pom.xml
|-src/test/
| :
`-target/
`-test-classes/
|-fizz.txt
|-foo/
| `-bar.txt
`-example/
`-HelloTest.class
-
target/test-classes
の下に結果が出力されている
- 整理すると、次のような動作になっている
-
testResources
ゴールは、${project.basedir}/src/test/resources
以下のファイルやフォルダを、${project.basedir}/target/test-classes
にコピーする- リソースフォルダは
<project><build><testResources>
で指定できる
- リソースフォルダは
-
testCompile
ゴールは、${project.basedir}/src/test/java
以下の Java ソースファイルをコンパイルして、${project.basedir}/target/test-classes
に出力する- ソースフォルダは、
<project><build><testSourceDirectory>
で指定できる
- ソースフォルダは、
テストを実行する
- 次は、コンパイルしたテストコードを実行するため、 maven-surefire-plugin の test ゴール が実行される。
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
</project>
- JUnit5 を動かすために以下の修正を入れている
-
junit-jupiter
を依存関係に追加 -
maven-surefire-plugin
のバージョンに 2.22.2 を指定- JUnit5 を使うには 2.22.0 以上を指定する必要がある
-
|-pom.xml
`-src/
|-main/java/
| `-example/
| `-Hello.java
`-test/java/
`-example/
`-HelloTest.java
- テスト対象のクラス(
Hello.java
)と、テストクラス(HelloTest.java
)を配置
> mvn test
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ hello ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running example.HelloTest
...
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.027 s - in example.HelloTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
> dir /b target\surefire-reports
example.HelloTest.txt
TEST-example.HelloTest.xml
-
surefire:test
ゴールはtest
フェーズに紐付いているので、test
フェーズを指定して実行している-
surefire:test
を単独で実行することも可能だが、その場合はソースのコンパイルは別途済ませておく必要がある
-
- 実行対象となるテストクラスは includes パラメータ で指定されており、デフォルトで次のクラスが対象になる
**/Test*.java
**/*Test.java
**/*Tests.java
**/*TestCase.java
- テストの結果は
target/surefire-reports
の下にテキストと xml 形式で出力される- これは、
surefire:test
ゴールの reportsDirectory パラメータ で指定されている(デフォルトは${project.build.directory}/surefire-reports
)
- これは、
テストをスキップする
-
test
フェーズの次はプロジェクトのコンパイル結果を jar に固めるpackage
フェーズが実行される - つまり、 jar を生成するためには
test
フェーズが正常終了する必要がある-
test
フェーズでテストが失敗した場合は、package
フェーズは実行されない
-
- テストが通っていないコードを jar に固めても使い物にならない、というのが本来あるべき姿だが、実際はテストコードがメンテされておらずテストが通らないというプロジェクトも残念ながら存在する
- そういうときは、
test
フェーズだけ実行をスキップしてpackage
フェーズを実行するということがよく行われる(本来はすべきではない)
> mvn -DskipTests package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ hello ---
[INFO] Tests are skipped.
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
-
surefire:test
ゴールには skipTests というパラメータが用意されている - これが設定されていると、テストの実行がスキップされるようになる
- 整理すると、
test
フェーズでは次のことが行われる-
maven-surefire-plugin
によってテストコードが実行される - JUnit 5 を使用するためには、
maven-surefire-plugin
のバージョンを 2.22.0 以上にしておく必要がある - 実行対象となるテストクラスは includes パラメータ で指定されている
- テストの結果は
target/surefire-reports
の下に出力される(reportsDirectory パラメータ) - テストの実行は skipTests パラメータ を設定することでスキップできる
- ただし、これの使用は必要最小限に留めるべき
- テストがメンテナンスされておらず、今すぐ jar が必要(本来はテストが通るように修正するのが正しい対応)
- テストは通るが、実行に時間がかかるため毎回実行してられない
- etc
- ただし、これの使用は必要最小限に留めるべき
-
jar に固める
- コンパイル結果を jar に固める処理は、 maven-jar-plugin の jar ゴール によって行われる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
- コンパイルで必要になる最小限の設定にしている
|-pom.xml
`-src/
|-test/
| :
`-main/
|-java/
| `-example/
| `-Hello.java
`-resources/
`-hoge.txt
> mvn package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ hello ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ hello ---
...
[INFO] --- maven-surefire-plugin:2.22.2:test (default-test) @ hello ---
...
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
-
jar:jar
ゴールはpackage
フェーズに紐付いているので、package
フェーズを実行している
|-pom.xml
|-src/
| :
`-target/
|-hello-1.0.0.jar
:
-
target
フォルダの直下に、hello-1.0.0.jar
が出力されている- jar ファイルの出力先は、 jar ゴールの outputDirectory パラメータによって設定されている
- このパラメータのデフォルトは、
${project.build.directory}
となっている(${project.basedir}/target
)
- このパラメータのデフォルトは、
- また、 jar ファイルの名前は jar ゴールの実装では finalName というフィールドで宣言されている
- このフィールドは
readonly = true
なので、外から直接変更することはできない - このフィールドの値には、
${project.build.finalName}
という値が設定されている - この値は Super POM によって
${project.artifactId}-${project.version}
がデフォルトで設定されている - したがって、 jar の名前を変更したい場合は、
<project><build><finalName>
を設定すればいい
- このフィールドは
- jar ファイルの出力先は、 jar ゴールの outputDirectory パラメータによって設定されている
- この jar を解凍すると、中身は次のようになっている
|-hoge.txt
|-example/
| `-Hello.class
`-META-INF/
|-MANIFEST.MF
`-maven/example/hello/
|-pom.properties
`-pom.xml
-
hoge.txt
とHello.class
は、それぞれresources:resources
,compiler:compile
によってtarget/classes
の下に出力されたものが梱包されている- どのフォルダの内容が jar に梱包されるかは、 jar ゴールの classesDirectory パラメータによって設定されている
- このパラメータのデフォルトは、
${project.build.outputDirectory}
となっている(${project.basedir}/target/classes
)
- このパラメータのデフォルトは、
- どのフォルダの内容が jar に梱包されるかは、 jar ゴールの classesDirectory パラメータによって設定されている
-
META-INF
の下に pom.xml が出力されているが、その内容はこのプロジェクトの pom.xml と同じものが入っている -
MANIFEST.MF
とpom.properties
は、それぞれ次のような内容になっている
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.6.3
Built-By: xxxxx
Build-Jdk: 11.0.6
#Generated by Maven
#Sun Mar 29 21:59:48 JST 2020
groupId=example
artifactId=hello
version=1.0.0
- これらは、 jar ゴールの archive パラメータで設定する
-
archive
の設定の詳細は Apache Maven Archiver – Reference で確認できる - デフォルトで有効になっている設定で、これらのファイルが生成されている
- 整理すると、
jar
ゴールは次のように動作する - ファイル名
${project.artifactId}-${project.version}
- 変更する場合は
<project><build><finalName>
で指定
- 出力場所
${project.basedir}/target
- 変更する場合は
<project><build><directory>
で指定
- 梱包対象
${project.basedir}/target/classes
- 変更する場合は、
<project><build><outputDirectory>
かjar
ゴールのclassesDirectory
パラメータで指定 - さらに
archive
パラメータでMANIFEST.MF
やMETA-INF
以下に追加される情報を設定できる
ローカルリポジトリにインストールする
-
install
フェーズに紐付けられている maven-install-plugin の install ゴール は、ローカルリポジトリにプロジェクトの成果物をインストールする機能を提供する
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
...
</project>
> mvn install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
-
install:install
ゴールはinstall
フェーズに紐付いているので、install
フェーズを実行している(ややこしい) - 実行が完了したら、ローカルリポジトリの中を確認する
- ローカルリポジトリの場所は、 Windows ならデフォルトは
%USERPROFILE%\.m2\repository
(Linux系のOSなら$HOME/.m2/repository
) -
settings.xml
で別の場所を指定している場合は、そちら
- ローカルリポジトリの場所は、 Windows ならデフォルトは
ローカルリポジトリ/
|-example/
: `-hello/
|-1.0.0/
: |-hello-1.0.0.jar
:
- ローカルリポジトリの中に jar ファイルが保存されている
- 登録される jar ファイルは、多分
package
フェーズで生成される jar ファイル-
install:install
ゴールのドキュメントではproject's main artifact
と書かれていたが、具体的に何がproject's main artifact
になるのか、明確に書かれているところを見つけることはできなかった - 実装的には MavenProject の getArtifact() で取得できる Artifact の getFile() で取得できるファイルがインストールされているっぽいところまでは読み解けたが、この
File
オブジェクトが設定されているところを見つけきれなかった
-
- ローカルリポジトリにインストールされたアーティファクトは、ローカルの別のプロジェクトで参照できるようになる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>foo</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
-
hello-1.0.0.jar
への依存関係を<dependencies>
で宣言している
package example;
public class Foo {
public static void main(String... args) {
new Hello().hello();
}
}
-
Hello
クラスを使った実装を書いている
> mvn compile
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ foo ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ foo ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
- コンパイルが通った(
Hello
クラスが解決できている)
- 整理すると、
install
ゴールは次のように動作する - おそらく
package
フェーズで生成された成果物(jar ファイルなど)を、ローカルリポジトリにインストールする - インストールされたアーティファクトは、ローカルの他のプロジェクトから依存対象として参照できるようになる
デプロイする
- 最後に実行されるのが
deploy
フェーズとなる -
deploy
フェーズでは、 maven-deploy-plugin の deploy ゴール が実行される -
deploy
ゴールは、プロジェクトの成果物をリモートのリポジトリにデプロイする -
環境構築とかが面倒な割に実際に使う機会は少ない気がするので検証は省略
Web プロジェクトのビルド
- packaging を
war
にした場合に、プロジェクトがどのようにビルドされるかを見る - ただ、
default
フェーズで実行されるプラグインは、jar
の場合とほとんど違いはない -
jar
と異なるのは、package
フェーズで実行されるゴールが war プラグインの war ゴール ということだけ
|-pom.xml
`-src/main/
|-java/
| `-example/webapp/
| `-HelloServlet.java
`-webapp/
`-WEB-INF/
`-hello.jsp
-
src/main/webapp
というフォルダを作成し、その下にwar
に含めるWEB-INF
などのフォルダを作成している
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>webapp</artifactId>
<version>1.0.0</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
</plugin>
</plugins>
</build>
</project>
-
<packaging>
にwar
を指定している - 依存関係として次の2つを指定している
- Servlet API
- Apache Commons Lang3
> mvn package
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ webapp ---
...
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ webapp ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ webapp ---
...
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ webapp ---
...
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ webapp ---
...
[INFO] --- maven-war-plugin:3.2.3:war (default-war) @ webapp ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
-
jar
の場合と同様に、各フェーズでプラグインのゴールが実行されていき、最後にwar:war
が実行されている
|-pom.xml
|-src/
| :
`-target/
`-webapp-1.0.0.war
- war ファイルは、
target
の直下に生成されている- 出力位置は
war
ゴールの outputDirectory パラメータ で指定されており、デフォルトで${project.build.directory}
となっている - また、 war ファイルの名前は warName という読み取り専用のパラメータで指定されており、デフォルトが
${project.build.finalName}
となっている
- 出力位置は
-
webapp-1.0.0.war
の中身は次のようになっている
webapp-1.0.0.war/
|-WEB-INF/
| |-hello.jsp
| |-classes/
| | `-example/webapp/
| | `-HelloServlet.class
| `-lib/
| `-commons-lang3-3.10.jar
`-META-INF/
|-MANIFEST.MF
`-maven/example/webapp/
|-pom.properties
`-pom.xml
-
src/main/java
のコンパイル結果とsrc/main/webapp
の下の内容が war ファイルの中に入っているのが分かる- class ファイルやライブラリの jar 以外で war の中に入れるファイルは、
src/main/webapp
フォルダの下に配置する - これは
war
ゴールの warSourceDirectory パラメータ で指定されており、デフォルトが${basedir}/src/main/webapp
となっている
- class ファイルやライブラリの jar 以外で war の中に入れるファイルは、
- また、依存関係に指定していた Apache Commons Lang3 の jar も
WEB-INF/lib
の下に格納されている- Servlet API の jar は格納されていない
- これは、 Servlet API への依存のスコープを
provided
に指定していたため - スコープの詳細な説明は後述
- 整理すると、 packaging が
war
の場合、プロジェクトは次のようにビルドされる- 基本は
jar
と同じようにビルドされる - ただし、
package
フェーズだけwar:war
ゴールが実行される点がjar
の場合と異なる -
war:war
ゴールでは、プロジェクトのコンパイル結果が war ファイルに固められて出力される - war の中には class ファイル・リソース・依存ライブラリに加えて、
src/main/webapp
の下に配置したファイルもそのまま格納される
- 基本は
Java アプリケーションを実行する
package example;
import java.util.Arrays;
public class Hello {
public static void main(String... args) {
System.out.println("Hello World! args=" + Arrays.toString(args));
}
}
- Hello World して、コマンドライン引数を出力している実装
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<mainClass>example.Hello</mainClass>
<arguments>
<argument>one</argument>
<argument>two</argument>
<argument>three</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- exec-maven-plugin というプラグインを追加している
> mvn compile exec:java
...
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ hello ---
Hello World! args=[one, two, three]
...
-
Hello.java
が実行された -
exec-maven-plugin
の java ゴール を使用すると、プロジェクトのビルド結果をクラスパスに入れて Java プログラムを実行できる-
exec-maven-plugin
には任意のコマンドを実行できる exec ゴール も用意されているが、ここでは割愛
-
- mainClass パラメータで Main クラスを指定することで、 Java プログラムを実行できる
-
arguments パラメータで、コマンドライン引数を渡すことができる
-
commandlineArgs
というパラメータでも引数を渡すことができるが、こちらは後述するコマンドラインから実行する場合に利用するためにある(たぶん)
-
コマンドラインから実行する
- 最初の例は、あらかじめ pom.xml に実行の構成をすべて記述しておく必要がある
- 場合によっては、コマンドラインで引数を色々変更しながら実行したくなることもあるかもしれない
- その場合は、システムプロパティを介してゴールのパラメータを指定する方法をとるとやりやすい
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<mainClass>example.Hello</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- Main クラスをコマンドラインから指定するのは面倒なので、これだけはあらかじめ pom.xml に記述しておいていいと思う
> mvn compile exec:java -Dexec.args="ONE TWO THREE"
...
Hello World! args=[ONE, TWO, THREE]
- commandlineArgs パラメータを使えば、引数をスペース区切りの文字列で指定できる
- システムプロパティで指定する場合は、
exec.args
で指定する
依存関係
依存関係のスコープ
- 依存関係には、**スコープ(Scope)**というものを設定できる
- スコープとは、その依存関係を使用する範囲を表す
- 例えば、
compile
スコープに指定されている依存関係は、ソースコードをコンパイルするときからアプリケーションを実行するときまで、常に使用することを表している - また、
test
スコープが指定されている依存関係は、テストコード(src/test/java
)のコンパイルとテストの実行時にだけ使用することを表している - スコープは、次の6種類存在する
compile
provided
runtime
test
system
import
compile スコープ
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
- スコープを指定していない場合、デフォルトで
compile
スコープが採用される(明示することも可能) -
compile
スコープは、コンパイルから実行まで、常にその依存関係が必要であることを表している
provided スコープ
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
-
provided
スコープは、その依存関係が実行環境から提供される(provided)ことを表している - 次の条件が満たされる場合に使用する
- コンパイルやテストでは必要になる
- しかし、実行時は実行環境が jar を提供してくれるので、アプリケーションが個別に保持しておく必要はない
- 最もよくある例として、 Servlet API が挙げられる
- Servlet API は、 war をデプロイするアプリケーション・サーバーが提供するものを使用するため、実行時にアプリケーションが個別に依存関係を持っておく必要はない
- 同様の理由で、 Java EE (Jakarta EE) が提供する他の API (EJB, JAX-RS, etc...)も
provided
で指定する
-
maven-war-plugin
で war を生成した場合、provided
に指定された依存関係はWEB-INF/lib
の下には配置されないようになっている
runtime スコープ
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.12</version>
<scope>runtime</scope>
</dependency>
-
runtime
スコープは、provided
の逆でコンパイル時には必要ないが実行時に必要な依存関係で使用する - 典型的な例として、 JDBC ドライバが挙げられる
- JDBC ドライバを使ってプログラムを書く場合、実装では標準 API が提供している java.sql.Connection などのクラスにだけ依存すればいい
- 各データベース製品が提供している具象クラスに直接依存することは基本的にはない
- しかし、当然実行時は実体が必要になる
- ということで、コンパイル時は必要ないが実行時は必要、というケースに当てはまることになる
test スコープ
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
-
test
スコープは、テストソースのコンパイルと実行時にだけ使用する依存関係で使用する - main のソースコードのコンパイルや、実行時には使用できない
- JUnit のような、テストのときだけ必要になる依存関係で使用する
system スコープ
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
-
system
スコープは、その依存関係がシステム(実行環境の JRE や JDK)から提供されるものであることを表す - このスコープは、リポジトリに存在せず JRE や JDK が内部に保持している拡張ライブラリなどを使用することを目的に用意されているものらしい
-
system
スコープを指定した場合は、<systemPath>
で対象の jar ファイルのパスを指定する必要がある - リポジトリに存在しないライブラリをプロジェクトの中に持たせておいて、それを参照するのに使ったりもできる
- 本来は、プライベートリポジトリを用意して、そこで管理するのが正攻法
- 一応、このスコープの利用は非推奨となっている
import スコープ
|-foo/
| `-pom.xml
`-bar/
`-pom.xml
- foo, bar の2つのプロジェクトが存在する
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>foo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- foo プロジェクトは packaging を
pom
にして、<dependencyManagement>
だけを宣言している - 依存対象として、次の2つを宣言している
org.apache.commons:commons-lang3:3.10
org.apache.commons:commons-text:1.8
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>bar</artifactId>
<version>1.0.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>example</groupId>
<artifactId>foo</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
- bar プロジェクトでは、
<dependencyManagement>
の中で、先程の foo プロジェクトをimport
スコープで指定している-
import
スコープは<dependencyManagement>
の中でしか指定できない -
import
スコープを指定した場合は、合わせて<type>pom</type>
も指定する必要がある
-
- また、それ以外の依存対象として、次の2つを宣言している
org.apache.commons:commons-lang3:3.9
org.apache.commons:commons-csv:1.8
- この状態で、 bar プロジェクトの
effective-pom
を確認する
> cd bar
> mvn help:effective-pom
...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>
</dependencies>
</dependencyManagement>
artifactId | foo | bar | effective-pom |
---|---|---|---|
commons-lang3 |
3.10 |
3.9 |
3.9 |
commons-csv |
- | 1.8 |
1.8 |
commons-text |
1.8 |
- | 1.8 |
- foo プロジェクトへの依存の宣言が消えて、代わりに foo プロジェクトの
<dependencyManagement>
で宣言していた依存関係が追加されている - ただし、アーティファクトが重複している場合は bar プロジェクトのバージョンが採用されている(
commons-lang3
) - このように、
import
は他の pom プロジェクトの<dependencyManagement>
を取り込む(importする)特殊なスコープとなっている
BOM
- マルチプロジェクトでの依存関係のバージョン管理手法として、
import
スコープを利用した BOM (bill of materials) という方法が存在する - BOM では、まず pom.xml を持つ BOM プロジェクトを用意する
- BOM プロジェクトの pom.xml には
<dependencyManagement>
が宣言されており、各プロジェクトで使用する依存関係が定義されている - 各プロジェクトでは、この BOM プロジェクトを
import
スコープで読み込む - 依存関係のバージョンは BOM プロジェクトで宣言されているので、各プロジェクトは
groupId
,artifactId
だけを<dependency>
に宣言すればいい - これにより、全プロジェクトで使用する依存関係のバージョンを BOM プロジェクトで一元管理できるようになる
- ちなみに、Spring Boot の BOM が こちら
- この BOM を読み込めば、使用するライブラリのバージョンを Spring Boot がサポートしているものに合わせることができる
- 各スコープの範囲を俯瞰できるようにするため、スコープとゴールの関連を表にしてみた(
import
は特殊なので除外)
-
○
が付いているのは、ゴールが該当するスコープの依存関係を参照・使用することを表している -
war:war
については、生成される war ファイル内に梱包されるかどうか、ということを意味している - それ以外は、クラスパスに設定されるかどうか、ということを意味している
依存関係をツリーで確認する
- プロジェクトの持つ依存関係をツリー構造のグラフで表示して確認できる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
</project>
- Spring Framework の web と jdbc を依存関係に宣言している
- しかし、プロジェクトの依存しているライブラリはこれだけではなく、実際は
spring-web
,spring-jdbc
が依存するライブラリにも依存している - 最終的にプロジェクトが依存しているライブラリがいくつあるのかは、 pom.xml を見ただけでは判断できない
- maven-dependency-plugin の tree ゴール を実行すると、この推移的な依存関係を含めたすべての依存関係をツリー構造のグラフで確認できる
> mvn dependency:tree
...
[INFO] +- org.springframework:spring-web:jar:5.2.5.RELEASE:compile
[INFO] | +- org.springframework:spring-beans:jar:5.2.5.RELEASE:compile
[INFO] | \- org.springframework:spring-core:jar:5.2.5.RELEASE:compile
[INFO] | \- org.springframework:spring-jcl:jar:5.2.5.RELEASE:compile
[INFO] \- org.springframework:spring-jdbc:jar:5.2.5.RELEASE:compile
[INFO] \- org.springframework:spring-tx:jar:5.2.5.RELEASE:compile
...
-
spring-web
が、さらにspring-beans
,spring-core
に依存し、さらにspring-core
がspring-jcl
に依存していることが分かる - そして、
spring-jdbc
はspring-tx
に依存していることがわかる- ちなみに、
spring-jdbc
はspring-beans
,spring-core
にも依存しているが、spring-web
側と重複するため表示は省略されている
- ちなみに、
- デフォルトでは全てのスコープの依存関係が出力されるが、 scope パラメータ で絞ることもできる
依存関係のjarを集める
- プロジェクトが依存する jar ファイルの実物を手元にすべてかき集めたい、となることが稀によくある
- これは、
maven-dependency-plugin
の copy-dependencies ゴール で実現できる - ↑の pom.xml と同じ設定で実行してみる
> mvn dependency:copy-dependencies
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
> dir /b target\dependency
spring-beans-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
spring-jdbc-5.2.5.RELEASE.jar
spring-tx-5.2.5.RELEASE.jar
spring-web-5.2.5.RELEASE.jar
-
target/dependency
フォルダの下に、依存する jar がすべて出力されている - デフォルトでは全てのスコープの jar が対象になるが、 includeScope パラメータ などで絞り込むことができる
プロファイル
- 基本的にプロジェクトのビルドは、どの環境でも同じ設定で同じ結果が出力されるようにしておくのがいい
- ある環境ではビルドは成功するけど、別の環境ではエラーになる、なんてことになると色々面倒
- しかし、実際に開発をしていると、環境に依存したビルド設定が必要になることが稀によくあるらしい(しらんけど)
- ビルド環境によって pom.xml の内容を切り替えたい(特定のプラグインを有効にしたり、設定値を変更したり)場合に、**プロファイル(Profile)**という仕組みが利用できる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<profiles>
<profile>
<id>foo</id>
<properties>
<message>foo profile!!</message>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.10</version>
</dependency>
</dependencies>
<build>
<directory>${project.basedir}/build</directory>
</build>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>message = ${message}</echo>
<echo>project.build.directory = ${project.build.directory}</echo>
<echo>dependency[0] = ${project.dependencies[0].artifactId}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<profiles><profile>
で、foo
というプロファイルを宣言している -
foo
プロファイルでは、プロパティや依存関係、project.build.directory
の設定などを行っている -
maven-antrun-plugin
を使って、それらの値を表示するようにしている
> mvn antrun:run
...
[echo] message = ${message}
[echo] project.build.directory = F:\tmp\maven\hello\target
[echo] dependency[0] = ${project.dependencies[0].artifactId}
...
> mvn -P foo antrun:run
...
[echo] message = foo profile!!
[echo] project.build.directory = F:\tmp\maven\hello\build
[echo] dependency[0] = commons-lang3
- 普通に実行した場合は、
foo
プロファイルで設定した内容は反映されていない - 一方で、
-P foo
を指定して実行した場合は、foo
プロファイルで設定した内容が反映されているのが分かる - このように、プロファイルを使用すると、そのプロファイルを使用したときだけ適用される設定を定義できる
- プロファイルの定義は、
<profile>
で記述する-
<id>
で、そのプロファイルを一意に識別するための名前を設定する - それ以外は、基本的に pom.xml に記述できる要素(
<dependencies>
,<build>
など)をそのまま記述できる
-
- プロファイルの指定は、コマンドラインで
-P
に続けてプロファイルの<id>
を指定する
複数のプロファイルを指定して実行する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>foo</id>
<properties>
<foo>FOO</foo>
<message>foo profile!!</message>
</properties>
</profile>
<profile>
<id>bar</id>
<properties>
<bar>BAR</bar>
<message>bar profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<configuration>
<target>
<echo>foo = ${foo}</echo>
<echo>bar = ${bar}</echo>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
foo
とbar
という2つのプロファイルを宣言している -
message
は両方のプロファイルで宣言して重複するようにしている
> mvn -P bar,foo antrun:run
...
[echo] foo = FOO
[echo] bar = BAR
[echo] message = bar profile!!
- プロファイルを複数指定する場合は、カンマ区切りで
id
を並べる - 重複する要素は、 pom.xml 上で後に来る方が採用されるっぽい(プロファイルの指定順ではなさそう)
プロファイルをデフォルトで有効にする
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>foo</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<message>foo profile!!</message>
</properties>
</profile>
<profile>
<id>bar</id>
<properties>
<message>bar profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
...
<configuration>
<target>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
foo
プロファイルの方に、<activeByDefault>true</activeByDefault>
を設定している
> mvn antrun:run
...
[echo] message = foo profile!!
...
> mvn -P bar antrun:run
...
[echo] message = bar profile!!
-
foo
プロパティがデフォルトで有効になっている -
<activation><activeByDefault>
にtrue
を設定すると、そのプロファイルはデフォルトで有効になる -
-P
で別のプロファイルが明示された場合は、<activeByDefault>
がtrue
のプロファイルは無効になる
条件が満たされたときだけプロファイルを有効にする
システムプロパティが宣言されていたら有効にする
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>foo</id>
<activation>
<property>
<name>foo</name>
</property>
</activation>
<properties>
<message>foo profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
...
<configuration>
<target>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<activation>
の中で、<property>
を宣言している
> mvn antrun:run
...
[echo] message = ${message}
...
> mvn -Dfoo antrun:run
...
[echo] message = foo profile!!
-
-P
によるプロファイルの指定はしていないが、-Dfoo
でシステムプロパティを宣言することでfoo
プロファイルが有効になっている -
<activation>
の中では、そのプロファイルを有効にする条件を定義できる -
<property>
は、システムプロパティが宣言されているか、特定の値が設定されているかを条件に指定できる - ここでは
<name>foo</name>
だけを設定しているので、システムプロパティfoo
が設定されていれば、値に関わらずプロファイルが有効になる - 値も条件にする場合は、次のように
<value>
も宣言する
<property>
<name>foo</name>
<value>enable</value>
</property>
> mvn -Dfoo antrun:run
...
[echo] message = ${message}
...
> mvn mvn -Dfoo=enable antrun:run
...
[echo] message = foo profile!!
- システムプロパティ
foo
の値がenable
のときにだけ、foo
プロファイルが有効になっている
JDK のバージョンを条件にする
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>jdk11</id>
<activation>
<jdk>11</jdk>
</activation>
<properties>
<message>jdk11 profile!!</message>
</properties>
</profile>
<profile>
<id>jdk14</id>
<activation>
<jdk>14</jdk>
</activation>
<properties>
<message>jdk14 profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
...
<configuration>
<target>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<activation>
で<jdk>
要素を使用している
> java --version
openjdk 11.0.6 2020-01-14
...
> mvn antrun:run
...
[echo] message = jdk11 profile!!
...
> java --version
openjdk 14 2020-03-17
...
> mvn antrun:run
...
[echo] message = jdk14 profile!!
- 実行時の JDK のバージョンに合わせてプロファイルが切り替わっている
-
<jdk>
を使用すると、実行時の Java のバージョンをプロファイル適用の条件にできる -
<jdk>
に記述した条件は前方一致で Java のバージョンと比較される-
11
と指定してる場合、前方一致なので11.0.6
にもマッチする - さらに
(,11]
のような範囲指定の記述も可能(この場合は、 11 以下のバージョンにマッチする) - 範囲指定の書き方については Apache Maven Enforcer Built-In Rules – Version Range Specification を参照
-
- なお、比較対象となる Java のバージョンは、システムプロパティ
java.version
で取得された値が使用されている- 実装はたぶん これ
-
<jdk>!11</jdk>
のように、先頭に!
をつけると条件を否定できる
OS を条件にする
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>Windows</id>
<activation>
<os>
<name>Windows 10</name>
</os>
</activation>
<properties>
<message>Windows profile!!</message>
</properties>
</profile>
<profile>
<id>Linux</id>
<activation>
<os>
<name>Linux</name>
</os>
</activation>
<properties>
<message>Linux profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
...
<configuration>
<target>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<activation>
で<os>
を設定している
> mvn antrun:run
...
[echo] message = Windows profile!!
> mvn antrun:run
...
[echo] message = Linux profile!!
- 実行時の OS によって、適用されるプロファイルが切り替わっている
-
<os>
を使用すると、実行時に OS をプロファイル適用の条件に使用できる -
<name>
は、 OS の名前を条件にしている- OS の名前はシステムプロパティの
os.name
で取得できる値が使用されている - 大文字・小文字の区別はないっぽい
- OS の名前はシステムプロパティの
- 名前以外にも、次の条件を指定できる
-
<family>
- OS の種類
- 指定できる値の一覧は Apache Maven Enforcer Built-In Rules – Require OS Version を参照
- ちなみに、 Linux の場合は
unix
を指定する
-
<arch>
- CPU のアーキテクチャ
-
x86
とかamd64
を指定する
-
<version>
- OS のバージョン
-
- これらの条件を複数指定した場合は、全て AND 条件で結合される
- 今の環境がどういう値になるのかは、 maven-enforcer-plugin の display-info ゴール を実行することで確認できる
> mvn enforcer:display-info
...
[INFO] Maven Version: 3.6.3
[INFO] JDK Version: 11.0.6 normalized as: 11.0.6
[INFO] OS Info: Arch: amd64 Family: windows Name: windows 10 Version: 10.0
ファイルの有無を条件にする
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>Exists</id>
<activation>
<file>
<exists>${basedir}/pom.xml</exists>
</file>
</activation>
<properties>
<message>Exists profile!!</message>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
...
<configuration>
<target>
<echo>message = ${message}</echo>
</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<activation>
で<file>
を指定している
> mvn antrun:run
...
[echo] message = Exists profile!!
-
<file>
を使用すると、ファイルの有無をプロファイル適用の条件に使用できる -
<exists>
は、そのファイルが存在することを条件に設定する - ファイルが存在しないことを条件にする場合は、
<missing>
を使用する - ファイルパスの指定では、埋め込みパラメータとして
${basedir}
またはシステムプロパティ・リクエストプロパティ?しか使用できないという制限がある-
${project.basedir}/pom.xml
とか書いていると、うまく判定されない
-
使用できるプロファイルを確認する
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<profiles>
<profile>
<id>foo</id>
<properties>
<message>foo profile!!</message>
</properties>
</profile>
<profile>
<id>bar</id>
<properties>
<message>bar profile!!</message>
</properties>
</profile>
</profiles>
...
</project>
-
foo
,bar
、2つのプロファイルを定義している
> mvn help:all-profiles
...
[INFO] Listing Profiles for Project: example:hello:jar:1.0.0
Profile Id: bar (Active: false , Source: pom)
Profile Id: foo (Active: false , Source: pom)
- maven-help-plugin の all-profiles を実行すると、使用できるプロファイルを確認できる
アクティブなプロファイルを確認する
> mvn -P foo help:active-profiles antrun:run
...
Active Profiles for Project 'example:hello:jar:1.0.0':
The following profiles are active:
- foo (source: example:hello:1.0.0)
...
[echo] message = foo profile!!
- active-profiles を使用すると、実行時にアクティブ(有効)になっているプロファイルを確認できる
- デバッグのときとかに便利かも
いつプロファイルを使うべきか
- プロファイルを使うと、プロファイルごとに異なるビルド結果を作り出すことができるようになる
- このため、プロファイルを多様しすぎると、開発者によって異なるビルド結果が得られてしまうことになりかねない
- 「開発用途では
develop
プロファイルを有効にしないといけない」みたいなルールがあって、その指定を忘れていたなど
- 「開発用途では
- ビルド結果が変わるのは、混乱の原因になりかねない
-
<os>
などの条件設定が可能なことから想像できるように、プロファイルの本来の目的は、ビルドを行う環境の差異を吸収することにあると思う- したがって、それ以外の用途で用いるのは極力避けたほうが良い気がする(私見)
- 例えば、開発用・検証環境用・本番環境用に使用する設定ファイルを切り替えるような方法では、プロファイルは使うべきではないのかもしれない
- アプリの設定を環境ごとに切り替える手段は、最近は環境変数を使用するのがベストプラクティスとされている気がする
- 参考:III. 設定 | The Twelve-Factor App (日本語訳)
プラグインを自作する
- プラグインは自作できる
-
org.apache.maven.plugins
やcom.codehaus.mojo
にあるプラグインで目的が達成できない場合は、自分でプラグインを作ることになる
Hello World
<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>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
<packaging>maven-plugin</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
</plugin>
</plugins>
</build>
</project>
- packaging を
maven-plugin
にする - 依存関係として、次の2つを設定する
org.apache.maven:maven-plugin-api
-
org.apache.maven.plugin-tools:maven-plugin-annotations
- こちらは
provided
スコープで指定している
- こちらは
-
maven-plugin-plugin
を設定している- Java 11 でコンパイルしようとすると、このプラグインのバージョンを新しくしておかないとエラーになるっぽい?
package example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
public void execute() throws MojoExecutionException {
getLog().info("Hello Custom Plugin!");
}
}
-
AbstractMojo
クラスを継承してクラスを作成する- この
HelloPlugin
クラスが、1つのゴールに対応する
- この
-
execute()
メソッドの中で、そのプラグインの処理を実装する -
@Mojo
アノテーションで、ゴールのメタ情報を設定している-
name
は、ゴールの名前になる
-
> mvn install
...
-
install
フェーズを実行して、作成したプラグインをローカルリポジトリにインストールする - インストールしたプラグインを、他のプロジェクトで使用してみる
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
<build>
<plugins>
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
</plugins>
</build>
</project>
-
<plugin>
にhello-maven-plugin
を設定している
> mvn hello:hello
...
[INFO] Hello Custom Plugin!
- 自作のプラグインが実行できた
プラグインの名前(artifactId)
...
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
...
- 自作プラグインの
artifactId
は、XXX-maven-plugin
とするのが慣例となっている - このパターンの
artifactId
にしておくと、XXX
の部分が自動的にプラグインのプレフィックスとしてメタ定義が作成される - 試しに、作成された jar ファイルの中を確認する
hello-1.0.0.jar/
|-example/
`-META-INF/
|-MANIFEST.MF
`-maven/
|-example/
`-plugin.xml
- この
plugin.xml
の中身を見ると、次のようになっている
...
<plugin>
<name>hello-maven-plugin</name>
<description></description>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
<goalPrefix>hello</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
...
-
<goalPrefix>
がhello
になっている - これによって、このプラグインは
hello:<ゴール>
というプレフィックス指定ができるようになっている - もし
foo-maven-plugin
というartifactId
にしていれば、プレフィックスはfoo
になっている
Mojo
- Maven のプラグインのゴールを実装するクラスのことを、**Mojo(Maven Old Java Object)**と呼ぶ
- Maven のドキュメントを読んでいると、ところどころでこの Mojo という用語が現れる
- その場合は、ゴールを実装したクラスのこと、と思えばいいと思う
パラメータを定義する
package example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
@Parameter
private String message;
public void execute() throws MojoExecutionException {
getLog().info("message = " + message);
}
}
-
message
フィールドを Mojo に追加して、@Parameter
アノテーションを付けている - Getter, Setter は定義していない
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<message>Hello World!!</message>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<configuration>
で、 Mojo のフィールド名と同じ要素を宣言している
> mvn hello:hello
...
[INFO] message = Hello World!!
- Mojo で宣言した
message
フィールドを、パラメータとして指定できるようになっている - ちなみに、この状態だとシステムプロパティで値を指定することはできない
システムプロパティでパラメータを指定できるようにする
package example;
...
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
@Parameter(property="hello.message")
private String message;
public void execute() throws MojoExecutionException {
getLog().info("message = " + message);
}
}
-
@Parameter
のproperty
に、システムプロパティで指定するときの名前を設定する
...
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
...
-
<configuration>
の指定はしないようにする(設定していると、こちらが優先される)
> mvn hello:hello -Dhello.message="HELLO WORLD!!"
...
[INFO] message = HELLO WORLD!!
- システムプロパティ経由で値を設定できるようになった
パラメータのデフォルト値を設定する
package example;
...
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
@Parameter(property="hello.message", defaultValue="Hello Custom Plugin!!")
private String message;
public void execute() throws MojoExecutionException {
getLog().info("message = " + message);
}
}
-
@Parameter
アノテーションのdefaultValue
で、そのパラメータのデフォルト値を宣言できる
...
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
...
-
<configuration>
は未設定にしている
> mvn hello:hello
...
[INFO] message = Hello Custom Plugin!!
...
> mvn hello:hello -Dhello.message=OVERRIDE!!
...
[INFO] message = OVERRIDE!!
- 何も設定されていない場合は、
defaultValue
で設定した値が採用されているのがわかる
デフォルト値に式を使用する
package example;
...
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
@Parameter(defaultValue="${hello.mojo.message}")
private String message;
public void execute() throws MojoExecutionException {
getLog().info("message = " + message);
}
}
-
defaultValue
の値には、${...}
のようにして式を記述できる - 式の中で参照できる値については PluginParameterExpressionEvaluator の Javadoc を参照
- システムプロパティやプロジェクトのプロパティ(
<properties>
)も参照できる
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<properties>
<hello.mojo.message>Project Property</hello.mojo.message>
</properties>
<build>
<plugins>
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
</plugin>
</plugins>
</build>
</project>
-
<properties>
でdefaultValue
の式で宣言されていたものと同じキーで値を宣言している
> mvn hello:hello
...
[INFO] message = Project Property
...
> mvn hello:hello -Dhello.mojo.message="System Property"
...
[INFO] message = System Property
-
<properties>
で宣言された値がmessge
パラメータに設定されている - システムプロパティで上書きすることもできている
パラメータに使用できる型
- パラメータの型は、
String
だけでなく様々な型で宣言できる
package example;
import java.io.File;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
@Mojo(name="hello")
public class HelloMojo extends AbstractMojo {
@Parameter
private int intValue;
@Parameter
private long longValue;
@Parameter
private boolean booleanValue;
@Parameter
private double doubleValue;
@Parameter
private Date dateValue;
@Parameter
private File fileValue;
@Parameter
private URL urlValue;
@Parameter
private HelloEnum enumValue;
@Parameter
private List<String> listValues;
@Parameter
private Map<String, String> mapValue;
public void execute() throws MojoExecutionException {
Log log = getLog();
log.info("intValue=" + intValue);
log.info("longValue=" + longValue);
log.info("booleanValue=" + booleanValue);
log.info("doubleValue=" + doubleValue);
log.info("dateValue=" + dateValue);
log.info("fileValue=" + fileValue);
log.info("urlValue=" + urlValue);
log.info("enumValue=" + enumValue);
log.info("listValues=" + listValues);
log.info("mapValue=" + mapValue);
}
public enum HelloEnum {
HELLO,
WORLD;
}
}
- 様々な型でパラメータを定義している
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<intValue>123</intValue>
<longValue>1234567890</longValue>
<booleanValue>true</booleanValue>
<doubleValue>1234.5678</doubleValue>
<dateValue>2019-10-20 12:13:14</dateValue>
<fileValue>foo/bar</fileValue>
<urlValue>https://www.google.co.jp/</urlValue>
<enumValue>HELLO</enumValue>
<listValues>
<aaa>fizz</aaa>
<bbb>buzz</bbb>
</listValues>
<mapValue>
<foo>FOO</foo>
<bar>BAR</bar>
</mapValue>
</configuration>
</plugin>
</plugins>
</build>
</project>
> mvn hello:hello
...
[INFO] intValue=123
[INFO] longValue=1234567890
[INFO] booleanValue=true
[INFO] doubleValue=1234.5678
[INFO] dateValue=Sun Oct 20 12:13:14 JST 2019
[INFO] fileValue=F:\tmp\maven\hello\foo\bar
[INFO] urlValue=https://www.google.co.jp/
[INFO] enumValue=HELLO
[INFO] listValues=[fizz, buzz]
[INFO] mapValue={bar=BAR, foo=FOO}
...
-
int
やlong
,float
,double
,boolean
などのプリミティブ型は、普通に使用可能- ラッパークラスも可能
-
java.util.Date
は、次のいずれかの書式で指定する-
yyyy-MM-dd HH:mm:ss.S a
(例:2005-10-06 2:22:55.1 PM
) -
yyyy-MM-dd HH:mm:ssa
(例:2005-10-06 2:22:55PM
) - ただし、完全に一致していなくても、そこそこ柔軟に解析してくれる(AM, PM は省略できるけど、時刻は省略できないっぽい)
-
-
java.io.File
は、値をパスとして扱う -
java.net.URL
は、値を URL として扱う - enum 型の場合は、列挙された定数と同じ値を指定することで設定可能
-
List
の場合は、パラメータ名と同じ要素を書いた中に任意の名前の要素を列挙すれば、それがList
の要素として処理されるっぽい- 普通は
<listValues><listValue>...
のように記述するのがわかりやすいと思う
- 普通は
-
Map
の場合は、入れ子要素の名前がキーに、要素の値がバリューになる- 検証は省略したけど、
java.util.Properties
も同じように使用できる
- 検証は省略したけど、
デフォルトのフェーズを紐付ける
package example;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
@Mojo(name="hello", defaultPhase=LifecyclePhase.VALIDATE)
public class HelloMojo extends AbstractMojo {
public void execute() throws MojoExecutionException {
getLog().info("Hello Mojo!!");
}
}
-
@Mojo
のdefaultPhase
で、デフォルトのフェーズを指定できる - フェーズの指定には、
LifecyclePhase
列挙型を使用する - ここでは
validate
フェーズに紐づけている
...
<plugin>
<groupId>example</groupId>
<artifactId>hello-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<goals>
<goal>hello</goal>
</goals>
</execution>
</executions>
</plugin>
...
-
hello
ゴールだけ宣言して、フェーズの紐付けは行っていない
> mvn validate
...
[INFO] Hello Mojo!!
-
validate
フェーズの実行でhello
ゴールが実行されている
生成物を削除する
|-pom.xml
`-target/
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>hello</artifactId>
<version>1.0.0</version>
</project>
> mvn clean
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
`-pom.xml
-
maven-clean-plugin の clean ゴール を実行すると、プロジェクトの生成物をすべて削除できる
- ここで指定しているのは
clean
フェーズ -
clean
ゴールは、デフォルトでclean
フェーズに紐付いている(ややこしい)
- ここで指定しているのは
- デフォルトでは、次のフォルダが削除対象となっている
${project.build.directory}
${project.build.outputDirectory}
${project.build.testOutputDirectory}
${project.reporting.outputDirectory}
- 特に上記フォルダのパスを変更していないのであれば、
${project.basedir}/target
が削除される
削除対象のフォルダを追加する
|-pom.xml
|-target/
`-foo/
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<filesets>
<fileset>
<directory>${project.basedir}/foo</directory>
</fileset>
</filesets>
</configuration>
</plugin>
</plugins>
</build>
</project>
-
<filesets>
を追加して、foo
フォルダも削除対象に追加している
> mvn clean
...
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ hello ---
[INFO] Deleting ...\foo (includes = [], excludes = [])
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
`-pom.xml
-
target
だけでなく、foo
も削除されている - filesets パラメータ で、削除対象のフォルダやファイルを追加できる
-
<filesets>
は、 Fileset のコレクションを指定する-
<directory>
だけでなく、<includes>
や<excludes>
でファイルを絞ることもできるっぽい(試してない)
-
参考兼公式ドキュメント目次
どこになんの情報があるのか分かりにくすぎるので整理してみる
- POM
- プラグイン
- ビルドライフサイクル
- プロファイル
- 依存関係の解決
- フォルダ構成
- Javadoc
-
Maven 1.0 が 2004 年、Maven 2.0 が 2005 年、Maven 3.0 が 2010 年(Maven – Maven Releases History) ↩
-
%USERPROFILE%
はユーザのホームを指す Windows の環境変数(Linux とかなら$HOME
のこと) ↩ -
「
<modelVersion>
も Super POM にあるやん」って思うけど、これは残念ながら継承されないもよう(消すとエラーになる) ↩ -
厳密には
compiler::::compile
のような指定の場合もプレフィックス指定と判定されるけど、ここはわかりやすさを優先してコロンの数としている(詳しくは MojoDescriptorCreator の実装を参照) ↩ -
<release>
はリリースバージョン内での最新なのに対して、<latest>
はスナップショットも含めて最新のバージョンを指している ↩ -
厳密には
settings.xml
の<pluginGroups>
で検索対象のgroupId
を追加できる(参考:Introduction to Plugin Prefix Resolution) ↩ -
In Maven 2.0.5 and above, multiple goals bound to a phase are executed in the same order as they are declared in the POM, ↩