Apache Maven 再入門

Zenn にも書籍として公開しています．
読みやすい方で読み進めていただけたらと思います．
Zenn の方が最新版です．

https://zenn.dev/caunus/books/apache-maven-introduction

## この記事の目的

入社当時に簡単にしか勉強していなかった Java 開発周りの知識を確かにするため，自分で Apache Maven のドキュメントを読んで動作確認 & まとめます．

読み返しやすいように，公式ドキュメントと記載順を若干変えて補足しています．

:::note info
こちらの記事を参考にして Apache Maven を活用した開発を行うことができます．
:::

:::note warn
こちらは[公式ドキュメント](https://maven.apache.org/index.html)をさらって記録に残した記事です．
Apache Maven の Tips 等に関する記事は既に世の中に多々ありますので，必要に応じて調べてみてください．
また，Apache Maven プロジェクトのアーキテクチャ[^MAVEN/REF]やコミュニティ[^MAVEN/COMMUNITY]，コントリビュート手順[^MAVEN/CONTRIBUTE]などはこちらの記事からは省きます．
:::

[^MAVEN/REF]: https://maven.apache.org/ref/3.9.6/
[^MAVEN/COMMUNITY]: https://maven.apache.org/community.html
[^MAVEN/CONTRIBUTE]: https://maven.apache.org/guides/development/guide-helping.html

## Apache Maven とは

![maven-logo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/973173ae-f513-04c6-acab-f2655b129da6.png)

> Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.[^MAVEN/INDEX]

[^MAVEN/INDEX]: https://maven.apache.org/index.html

Apache Maven は [Apache Software Foundation (ASF)](https://www.apache.org/) が管理する Java ベースのプロジェクト管理 OSS ツールです．

Maven ではプロジェクト構成や情報をプロジェクトオブジェクトモデル (POM, ```pom.xml```) に記載します．
Maven を使うことで POM と (今や標準規格となっている) 特有のプロジェクトレイアウトを利用して，ワークフロー・ビルドライフサイクルを活用できるようになります．
そのため，Maven を使った Java プロジェクトはベストプラクティスに沿って準備されていることが重要になります．

:::note info
現在では Maven の利用は広がっており，Java プロジェクトを理解できるようになりたい場合は，まずとある一つのプロジェクトで ```pom.xml``` を読めるようになるのが良いかと思います．
:::

Maven の強みは (推移的) 依存関係の管理にあり，任意のリリースバージョンの差し替えを (容易とまでは言いませんが) 強力にサポートします．
Git 等を用いることでコードとしてバージョン管理や変更差分の抽出，配布を補助することもできます．

また Java やスクリプト言語を利用したプラグインを開発・適用する方法も提供しています．
実は ```mvn``` コマンドで利用している ```clean``` などもすべて Maven プロジェクトがサポートしているプラグインとして機能実装されています．
プラグインは

- ビルド時に実行するもの
- サイト生成時に実行するもの

の2種類があります．[^MAVEN/PLUGINS]

[^MAVEN/PLUGINS]: https://maven.apache.org/plugins/index.html

## Apache Maven のユースケース

Maven が目指す機能として以下が挙げられています．

- ビルドプロセスの簡略化
- 標準ビルドシステムの提供
- プロジェクト情報の明示
- 開発体験の向上

実際に Apache Maven を利用すると，

- ひな形の Java プロジェクト作成
- Java プロジェクトの依存関係管理
- Java プロジェクトのビルド
- ビルドアーティファクトのデプロイ
- プロジェクトレポートの自動作成

が可能となり，集団あるいは個人の Java プロジェクト開発・公開を補助してくれます．

## 動作確認環境

動作確認を行った環境情報を以下に示します．

| 名称 | バージョン |
|----|----|
| Windows 11 | 22H2 |
| Ubuntu (WSL2) | 20.04.6 LTS |
| OpenJDK | 21 |
| Maven | 3.9.6 |

### Java & Maven 環境準備手順

- [OpenJDK](https://jdk.java.net/archive/)
- [Apache Maven](https://maven.apache.org/download.cgi)

それぞれからバイナリを含む tar.gz を取得して展開，パスを通します．

```shell
mkdir -p $HOME/installers/linux/java && cd $_ && \
wget https://download.java.net/java/GA/jdk21/fd2272bbf8e04c3dbaee13770090416c/35/GPL/openjdk-21_linux-x64_bin.tar.gz &&
mkdir -p $HOME/extracts &&
tar -xvf openjdk-21_linux-x64_bin.tar.gz --directory $HOME/extracts
vim $HOME/.bashrc
> # Java
> export JAVA_HOME=$HOME/extracts/jdk-21
> export CLASSPATH=$JAVA_HOME/lib
> export PATH=$PATH:$JAVA_HOME/bin
source $HOME/.bashrc
## 確認
java -version

# Maven
## 取得~パスを通す
mkdir -p $HOME/installers/linux/maven && cd $_ && \
wget https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz &&
tar -xvf apache-maven-3.9.6-bin.tar.gz --directory $HOME/extracts
vim $HOME/.bashrc
> # Apache Maven
> export MVN_HOME=$HOME/extracts/apache-maven-3.9.6
> export PATH=$PATH:$MVN_HOME/bin
source $HOME/.bashrc
## 確認
mvn -v
```

## Maven in 5 Minutes

公式の ["Maven in 5 Minutes"](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) に沿って動作確認をします．

```shell
# サンプルプロジェクト (アーキタイプ) の作成
mkdir -p $HOME/codes/GitHub && cd $_ &&
mvn archetype:generate
> ...
> Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 2099:
> Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
> 1: 1.0-alpha-1
> ...
> 8: 1.4
> Choose a number: 8:
> Define value for property 'groupId': com.caunus.sampleapp
> Define value for property 'artifactId': maven-training
> Define value for property 'version' 1.0-SNAPSHOT: :
> Define value for property 'package' com.caunus.sampleapp: :
> ...
> [INFO] ------------------------------------------------------------------------
> [INFO] BUILD SUCCESS
> [INFO] ------------------------------------------------------------------------

# pom.xml の確認，編集
cd maven-training && vim pom.xml

# アーティファクト (jar) 作成
mvn package

# アーティファクト (jar) 実行
java -cp ./target/maven-training-1.0-SNAPSHOT.jar com.caunus.sampleapp.App
> Hello World!
```

上記で ```pom.xml``` に記載してあるコンパイル目標 Java バージョンの指定を変更しています．

```diff_xml: pom.xml
...
  <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>
    <!-- Java >9 -->
+    <maven.compiler.release>21</maven.compiler.release>
  </properties>
...
```

```maven-archetype-archetype``` に基づいて作成されたプロジェクトの構成は以下，

```
maven-training
|-- pom.xml
|-- src
|   |-- main
|   |   `-- java
|   |       `-- com
|   |           `-- caunus
|   |               `-- sampleapp
|   |                   `-- App.java
|   `-- test
|       `-- java
|           `-- com
|               `-- caunus
|                   `-- sampleapp
|                       `-- AppTest.java
|
`-- target
```

標準のディレクトリレイアウトは以下です．

| 配置場所 | 配置物 |
|----|----|
| / | ```.git```, ```.svn``` 等 |
| / | ```LICENSE.txt``` |
| / | ```NOTICE.txt``` |
| / | ```README.txt``` |
| /src/main/java | アプリケーション/ライブラリのソース |
| /src/main/resources | アプリケーション/ライブラリのリソース |
| /src/main/filters | リソースフィルターファイル |
| /src/main/webapp | Web アプリケーションのソース |
| /src/test/java | テストソース |
| /src/test/resources | テストリソース |
| /src/test/filters | テストリソースフィルターファイル |
| /src/it | 統合テスト (主にプラグイン用) |
| /src/assembly | アセンブリ記述子 |
| /src/site | サイト |

サンプル (アーキタイプ) はプラグインプロジェクト，Webアプリプロジェクトなどにも準備されています．[^MAVEN/ARCHETYPES]

[^MAVEN/ARCHETYPES]: https://maven.apache.org/archetypes/index.html

```App.java``` は以下のように実装されています．

```java: App.java
package com.caunus.sampleapp;

/**
 * Hello world!
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}
```

## POM の読み方

```xml: 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>com.caunus.sampleapp</groupId>
  <artifactId>maven-training</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>maven-training</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.8</maven.compiler.source> -->
    <!-- <maven.compiler.target>1.8</maven.compiler.target> -->
    <maven.compiler.release>21</maven.compiler.release>
  </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>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
```

| タグ要素 | 用途 | 設定例 |
|----|----|----|
| project | 最上位要素 | - |
| project ><br> modelVersion | POM が利用するオブジェクトモデルのバージョン | 4.0.0 等 |
| project ><br> groupId | プロジェクト作成組織の識別子<br>組織の完全修飾ドメイン名 | プロジェクト作成時に指定 |
| project ><br> artifactId | 一意のアーティファクトベース名<br> -> アーティファクト (jar) ファイル名 | プロジェクト作成時に指定 |
| project ><br> version | アーティファクトのバージョン <br> -> アーティファクト (jar) ファイル名 | プロジェクト作成時に指定<br>1.0-SNAPSHOT 等 |
| project ><br> name | プロジェクトに使用される表示名 <br> -> Git プロジェクト名 | プロジェクト作成時に指定 (artifactId) |
| project ><br> url | プロジェクトのホストURL | http://www.example.com <br> 必要に応じて書き換える |
| project ><br> properties| POM 内横断で利用する設定値 | ```<project.build.sourceEncoding>```<br>```<maven.compiler.source>```<br>```<maven.compiler.target>``` |
| project ><br> dependencies | 依存関係を持つ"子"アーティファクト | junit 等の外部依存関係 <br> groupId/artifactId/version/scope を指定 |
| project ><br> build | プラグイン，リソース管理 | ```clean```, ```compile``` 等<br>ビルドライフサイクルに用いるタスクを指定 |

SNAPSHOT バージョンとは，開発ブランチに沿った開発バージョンを示します．
リリース時には ```-SNAPSHOT``` が取れます．

## 命名規則

以下の規則に従って命名していきます．

```groupId```: 全てのプロジェクトに渡って一意であり，[Java のパッケージ命名規則](https://docs.oracle.com/javase/specs/jls/se6/html/packages.html#7.7)に従う

- インターネットドメイン名を取得しておく
- ドメイン名に含まれるコンポーネントを反転してプレフィックスとして使う
- 以降は組織の規則に従う
  - 複数ジュールを扱うプロジェクトの場合は並列にする
- ドメイン名にハイフンや特殊文字が含まれる場合はアンダースコア ```_``` に変換する
- 数字もしくは特殊文字で始まるコンポーネントは先頭にアンダースコア ```_``` を付与する

```artifactId```: 小文字でわかりやすい名称，ハイフン利用可

```version```: 数字とドットを含む一般的なバージョン指定

- [Semantic Versioning](https://semver.org/) 等
- 日付は使用しない

## ビルドライフサイクル

組み込みで実装されているビルドライフサイクルには以下の3つがあります．

- Default
- Clean
- Site

それぞれが複数のフェーズで成り立っており，単一または複数の Apache 公式プラグインによって実装されています．

フェーズとは ```mvn``` コマンドで実行している ```clean``` や ```package``` 等で，ライフサイクルの中での段階を表します．
コマンドでフェーズが呼び出されると，Maven は呼び出されたフェーズまで順番に実行していきます．
例えば ```mvn package``` を実行すると，Default ビルドライフサイクルにて ```vaildate```, ```compile```, ```test``` フェーズを経由します．
ただし ```pre-*```, ```post-*```, ```process-*``` フェーズは通常コマンドで呼び出すことはせず，関連フェーズに紐づいて処理を実行します．

ゴールはプラグインの中で定義されている"処理単位"であり，フェーズと関連付けられて定義されています．
Maven では ```"プラグイン名:ゴール"``` の形式で ```mvn``` コマンドを叩くことで，ライフサイクルを無視してゴールだけを呼び出すこともできます．

```shell
# validate などのフェーズを無視して compile ゴールだけ実行する
mvn compiler:compile
```

ライフサイクル内のフェーズとデフォルトのプラグイン:ゴールとの紐づけが気になる場合は，以下をご参照ください．

https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#built-in-lifecycle-bindings

**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```** | **コンパイルされたコードを配布可能なパッケージ (jar 等) 化** |
| ```pre-integration-test``` | 統合テスト前に必要なプロセスの実行 |
| ```integration-test``` | 統合テストを実行できる環境にデプロイ |
| ```post-integration-test``` | 統合テスト後に必要なプロセスの実行 |
| **```verify```** | **パッケージが有効で品質基準を満たしていることを検証** |
| **```install```** | **ローカルリポジトリにインストール** |
| ```deploy``` | **リモートリポジトリにコピー** |

**Clean ライフサイクル**

| フェーズ | 用途 |
|----|----|
| ```pre-clean``` | 削除前に必要なプロセスの実行 |
| **```clean```** | **以前のビルドで生成されたファイルの削除** |
| ```post-clean``` | 削除後に必要なプロセスの実行 |

**Site ライフサイクル**

| フェーズ | 用途 |
|----|----|
| ```pre-site``` | サイト生成前に必要なプロセスの実行 |
| **```site```** | **サイトドキュメントの生成** |
| ```post-site``` | サイト生成完了 & デプロイに必要なプロセスの実行 |
| ```site-deploy``` | サイトドキュメントを指定の Web サーバーにデプロイ |

## POM の継承・集約

POM ファイルは継承や集約をすることができます．
ここで，継承は単一の親に対してのみ行うことができます．
複数の親から継承を行いたい場合は，[Dependency scope の import](#依存関係の管理) を利用します．

いま my-app を my-modlue の親とすることを想定します． 

```xml: my-app 用の pom.xml (親)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>
```

```xml: my-module 用の pom.xml (子)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>
```

**ケース 1**

親が子から観測可能である場合，

- [Example 1](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#example-1)
- [Example 3](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#example-3)

```
my-app
|-- pom.xml
`-- my-module
    `-- pom.xml
```

my-module 用の ```pom.xml``` (子) に ```parent``` タグ要素を入れることで，my-module (子) から my-app (親) を参照 (継承) できます．

```xml: my-module 用の pom.xml (子)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
  </parent>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>
```

また，my-app 用の ```pom.xml``` (親) に ```packaging``` タグ要素を入れることで，my-app (親) に my-module (子) を集約することもできます． 

```xml: my-app 用の pom.xml (親)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>my-module</module>
  </modules>
</project>
```

**ケース 2**

親と子が切り離されている場合，

- [Example 2](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#example-2)
- [Example 4](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#example-4)

```
.
|-- my-app
|   `-- pom.xml
`-- my-module
    `-- pom.xml
```

```relativePath``` タグ要素を使って相対パスを使うことで継承，

```xml: my-module 用の pom.xml (子)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
    <relativePath>../my-app/pom.xml</relativePath>
  </parent>
 
  <artifactId>my-module</artifactId>
</project>
```

あるいは集約を行うことができます．

```xml: my-app 用の pom.xml (親)
<project>
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>../my-module</module>
  </modules>
</project>
```

## プロファイルの設定

ローカルの状況に依存する設定や調整が必要な場合 (例えばプロキシ設定等)，プロファイルを与えて Maven を設定できます．
状況に応じて以下の場所に設定を記載します．

| ケース | プロファイル場所 |
|----|----|
| ユーザーごとに設定 | ```$HOME/.m2/settings.xml``` |
| グローバルに設定 | ```$HOME/extracts/apache-maven-3.9.6/conf/settings.xml``` |
| (参考) プロジェクトの設定 | ```pom.xml``` |

プロファイルを埋め込むことで移植性が損なわれたり，POM 継承などを行っている環境でどの設定が有効化されているか確認しなければならないなどの面倒が付きまとう可能性もあります．

詳しくは，[公式のプロファイル概要](https://maven.apache.org/guides/introduction/introduction-to-profiles.html)と[プロファイル設定項目](https://maven.apache.org/ref/3.9.6/maven-settings/settings.html)をご確認ください．

## リポジトリ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/3d5b65de-412d-6236-7035-cf760f3d9394.png)

https://maven.apache.org/repositories/index.html

リポジトリ (アーティファクトの格納場所) は以下の2つを利用します．

- リモートリポジトリ: インターネット上あるいは組織で公開するアーティファクトを格納
- ローカルリポジトリ: リモートリポジトリから取得したアーティファクト，リリースしていないアーティファクトを格納 (```mvn install``` 等)

ローカルリポジトリは，ディスクの容量問題などがない限りは特に作業を行うことはありません．
ローカルリポジトリはデフォルトで ```$HOME/.m2/repository/``` を使います．
特殊な事情でローカルリポジトリの場所を変更したい場合は，以下の設定を適切なプロファイルに記載します．

```xml: settings.xml
<settings>
  ...
  <localRepository>/path/to/local/repo/</localRepository>
  ...
</settings>
```

ローカルリポジトリに存在しないアーティファクトは，リモートリポジトリへ探しに行きます．
デフォルトでは [maven central repository](https://central.sonatype.com/) に問い合わせを行います．

他のリポジトリも利用したい場合は，適切なプロファイルに設定を記載します．
[Sonatype Nexus OSS](https://www.sonatype.com/products/sonatype-nexus-oss-download#main-content), [Gitea OSS](https://docs.gitea.com/), [Reposilite OSS](https://reposilite.com/guide/about) 等の[リポジトリマネージャー](https://maven.apache.org/repository-management.html)を独自でホストしている場合もこちらを使います．

```xml: pom.xml
<project>
...
  <repositories>
    <repository>
      <id>my-repo1</id>
      <name>your custom repo</name>
      <url>http://jarsm2.dyndns.dk</url>
    </repository>
    <repository>
      <id>my-repo2</id>
      <name>your custom repo</name>
      <url>http://jarsm2.dyndns.dk</url>
    </repository>
  </repositories>
...
</project>
```

```xml: settings.xml
<settings>
 ...
 <profiles>
   ...
   <profile>
     <id>myprofile</id>
     <repositories>
       <repository>
         <id>my-repo2</id>
         <name>your custom repo</name>
         <url>http://jarsm2.dyndns.dk</url>
       </repository>
     </repositories>
   </profile>
   ...
 </profiles>
 
 <activeProfiles>
   <activeProfile>myprofile</activeProfile>
 </activeProfiles>
 ...
</settings>
```

なお ```pom.xml``` と ```settings.xml``` どちらも設定した場合は，```settings.xml``` が優先されます．

maven central repository に関しても，地理的により近いミラーリポジトリに差し替えることができます．

```xml: settings.xml
<settings>
  ...
  <mirrors>
    <mirror>
      <id>other-mirror</id>
      <name>Other Mirror Repository</name>
      <url>https://other-mirror.repo.other-company.com/maven2</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
  </mirrors>
  ...
</settings>
```

ちなみにですが，maven central repository 全体の独自ミラーリポジトリを作成することは禁止されています．

インターネットに公開済みのアーティファクトの検索には [mvn repository](https://mvnrepository.com/) がしばしば利用されています．こちらではアーティファクトを検索して直接ダウンロードすることもできます．

なお，オフラインでビルドを行いたい場合は ```-o``` オプションを使うことができます．

```shell
mvn -o package
```

## 依存関係の管理

**推移的依存関係**

アーティファクトの依存関係をさらに読み取る構成を取ることで，Maven はマルチモジュールなアプリケーションでも毎回のすべての依存関係を記述することがなく利用できます．
また，爆発的に依存関係が大規模化してしまうことを防ぐ方法も準備されています．

- Dependency mediation

同じものを複数回参照した場合，ツリーで浅いものを優先して利用する "nearest definition"

```D 1.0 が採用される
  A
  ├── B
  │   └── C
  │       └── D 2.0
  └── E
      └── D 1.0
```

```D 2.0 を明示すれば D 2.0 が強制的に採用される
  A
  ├── B
  │   └── C
  │       └── D 2.0
  ├── E
  │   └── D 1.0
  │
  └── D 2.0  
```

プロジェクトがどのような依存関係を持っているかをツリーで確認する方法として ```mvn dependency:tree``` も準備されています．

```shell
mvn dependency:tree
> ...
> [INFO] com.caunus.sampleapp:maven-training:jar:1.0-SNAPSHOT
> [INFO] \- junit:junit:jar:4.11:test
> [INFO]    \- org.hamcrest:hamcrest-core:jar:1.3:test
> [INFO] ------------------------------------------------------------------------
> [INFO] BUILD SUCCESS
> ...
```

- Dependency scope

スコープの設定によって，推移的依存関係が解決されてクラスパスに含められるタイミングを指定することができます．
ただし，import スコープは POM の継承に利用します．

| スコープ | 用途 |
|----|----|
| compile | デフォルト |
| provided | JDK または コンテナ実行時にのみ含めたいものに指定<br>i.e. コンパイル時およびテスト時には含めるが，実行時には含めない |
| runtime | コンパイルには不要だが実行には含めたいものに指定<br>i.e. テスト時および実行時には含めるが，コンパイル時には含めない |
| test | アプリケーションには依存関係がなく，テストにのみ含めたいものに指定<br>i.e. テスト時には含めるが，コンパイル時および実行時には含めない |
| system | provided と含めるタイミングは同じだが，明示的に含めてありリモートレポジトリでは検索しない場合に指定 |
| import | ```<type>pom</type>``` に対してのみ指定できる<br>複数の POM から継承を行う |

- Optional dependencies

X -> A -> B 関係にあるとき，X で B の機能が使用されない場合は A から B の依存関係は張らなくてもよいということを，```optional``` タグ要素を使うことで明示的に設定することができます．

```xml: pom.xml
<project>
  ...
  <dependencies>
    <!-- declare the dependency to be set as optional -->
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <optional>true</optional> <!-- value will be true or false only -->
    </dependency>
  </dependencies>
</project>
```

- Excluded dependencies

X -> A -> B 関係にあるとき，X は B を意図的に依存関係から除外するということを，```exclusions``` タグ要素を使うことで明示的に設定することができます．

```xml: pom.xml
<project>
  ...
  <dependencies>
    <dependency>
      <groupId>sample.ProjectA</groupId>
      <artifactId>Project-A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
      <exclusions>
        <exclusion>  <!-- declare the exclusion here -->
          <groupId>sample.ProjectB</groupId>
          <artifactId>Project-B</artifactId>
        </exclusion>
      </exclusions> 
    </dependency>
  </dependencies>
</project>
```

[Banned dependencies](https://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html) を使えば，モジュールのブラックリストを作成し，合致したものが見つかってしまった場合はビルドが失敗するように設定することもできます．

## プラグイン作成

https://maven.apache.org/plugin-developers/index.html

Maven プラグインは Maven plain Old Java Object (Mojo) (※ 少し前話題になっていた [Python alternative の Mojo](https://www.modular.com/max/mojo) とは別物です！) を利用して作成することができます．

**命名規則**

```<yourplugin>-maven-plugin``` とします．
```maven-<yourplugin>-plugin``` は Apache Maven 公式プラグインで利用されているため NG です．

**作成 & 組み込み手順**

プラグイン用のアーキタイプを利用してプロジェクトを作成します．
本記事ではアーキタイプのサンプルのままでビルドを行います．
このサンプルプラグインは ```touch``` ゴールを持ち，```touch.txt``` を target に生成します．

```shell
cd $HOME/codes/GitHub &&
mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-plugin -DarchetypeVersion=1.4
> ...
Define value for property 'groupId': com.caunus.sampleplugin
Define value for property 'artifactId': touch-maven-plugin
Define value for property 'version' 1.0-SNAPSHOT: :
Define value for property 'package' com.caunus.sampleplugin: :
> ...
> [INFO] ------------------------------------------------------------------------
> [INFO] BUILD SUCCESS
> [INFO] ------------------------------------------------------------------------

# pom.xml の確認，編集
cd touch-maven-plugin && vim pom.xml

# アーティファクト (jar) 作成 & インストール
mvn clean install
> ...
> Installing /.../codes/GitHub/touch-maven-plugin/target/touch-maven-plugin-1.0-SNAPSHOT.jar to /.../.m2/repository/com/caunus/sampleplugin/touch-maven-plugin/1.0-SNAPSHOT/touch-maven-plugin-1.0-SNAPSHOT.jar
> ...

# maven-training に戻って依存関係に加える
cd $HOME/codes/GitHub/maven-training && vim pom.xml

# プラグインで実装した機能を実行
# ゴールだけを実行する (pom.xml に書いておかなくても動く)
mvn com.caunus.sampleplugin:touch-maven-plugin:1.0-SNAPSHOT:touch

# mvn package で touch ゴールも動くことを確認する
mvn package
> ...
> [INFO] --- jar:3.0.2:jar (default-jar) @ maven-training ---
> [INFO] Building jar: /.../codes/GitHub/maven-training/target/maven-training-1.0-SNAPSHOT.jar
> [INFO]
> [INFO] --- touch:1.0-SNAPSHOT:touch (default) @ maven-training ---
> [INFO] ------------------------------------------------------------------------
> [INFO] BUILD SUCCESS
> ...
```

上記で ```touch-maven-plugin``` 用の ```pom.xml``` に記載してあるコンパイル目標 Java バージョンの指定，```maven-plugin-plugin``` のバージョンを変更しています．

```diff_xml: プラグイン用の pom.xml
...
  <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>
    <!-- Java >9 -->
+    <maven.compiler.release>21</maven.compiler.release>
  </properties>
...
        <plugin>
          <artifactId>maven-plugin-plugin</artifactId>
-          <version>3.6.0</version>
+          <version>3.11.0</version>
        </plugin>
...
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-plugin-plugin</artifactId>
-        <version>3.6.0</version>
+        <version>3.11.0</version>
        <configuration>
          <!-- <goalPrefix>maven-archetype-plugin</goalPrefix> -->
          <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
        </configuration>
...
```

また，```maven-training``` 用の ```pom.xml``` には ```touch-maven-plugin``` が package フェーズに稼働するように設定しています．

```diff_xml: maven-training の pom.xml
...
  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        ...
+        <!-- custom plugin -->
+        <plugin>
+          <groupId>com.caunus.sampleplugin</groupId>
+          <artifactId>touch-maven-plugin</artifactId>
+          <version>1.0-SNAPSHOT</version>
+        </plugin>
      </plugins>
    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>com.caunus.sampleplugin</groupId>
+        <artifactId>touch-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>touch</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
  </build>
...
```

プラグインのゴールを定義している ```MyMojo.java``` は以下の様に実装されています．

```java: MyMojo.java
package com.caunus.sampleplugin;


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;
import org.apache.maven.plugins.annotations.Parameter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/**
 * Goal which touches a timestamp file.
 */
@Mojo( name = "touch", defaultPhase = LifecyclePhase.PROCESS_SOURCES )
public class MyMojo
    extends AbstractMojo
{
    /**
     * Location of the file.
     */
    @Parameter( defaultValue = "${project.build.directory}", property = "outputDir", required = true )
    private File outputDirectory;

    public void execute()
        throws MojoExecutionException
    {
        File f = outputDirectory;

        if ( !f.exists() )
        {
            f.mkdirs();
        }

        File touch = new File( f, "touch.txt" );

        FileWriter w = null;
        try
        {
            w = new FileWriter( touch );

            w.write( "touch.txt" );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "Error creating file " + touch, e );
        }
        finally
        {
            if ( w != null )
            {
                try
                {
                    w.close();
                }
                catch ( IOException e )
                {
                    // ignore
                }
            }
        }
    }
}
```

## サイトの生成

Maven はプロジェクトレポート (サイト) を簡単に生成することができます．

```shell
mvn site
```

上記を実行するとプロジェクト ```target``` 配下に html, css 等が生成されます．

```
maven-training
|-- pom.xml
|-- src
`-- target
    |-- site
    |   |-- css
    |   |-- images
    |   |-- surefire-reports
    |   |-- test-classes
    |   |-- dependencies.html
    |   |-- dependency-info.html
    |   |-- index.html
    |   |-- plugin-management.html
    |   |-- plugins.html
    |   |-- project-info.html
    |   `-- summary.html
    `-- ...
```

**dependencies.html**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/dd7cb427-979e-8c5f-f19a-0a4bbfb827df.png)

**dependency-info.html**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/d1ba6b3e-dc78-3cc6-7917-23fb106ed836.png)

一式で取得できるので，様々な方法を使って Web サーバーにデプロイすることができます．
GitHub Pages を利用したい場合，公式ドキュメントでは [Maven SCM Publish Plugin](https://maven.apache.org/plugins/maven-scm-publish-plugin/) を使ったデプロイ方法が紹介されています[^MAVEN/GITHUB-PAGES]が，GitHub Pages の設定に合わせて一式を丸々移すだけでも簡単にデプロイできます．

[^MAVEN/GITHUB-PAGES]: https://maven.apache.org/guides/mini/guide-site.html#github-pages-apache-svnpubsub-gitpubsub-deployment

```shell
# GitHub で maven-training リポジトリを作成後
# まずは普通に push
echo "# maven-training" >> README.md
echo "/target/*" >> .gitignore
git init && git add . && git commit -m "init"
git branch -m master main
git remote add origin https://github.com/caunu-s/maven-training.git
git push -u origin main

# orphan ブランチを作成して Pages 専用にする
git checkout --orphan gh-pages
mv target/site/* .
rm pom.xml README.md && rm -rf src && rm -rf target
git add . && git commit -m "init site" && git push origin gh-pages

# サイト更新，main ブランチで mvn package 後を想定
git checkout gh-pages
rm *.html && rm -rf css && rm -rf images
mv target/site/* . && rm -rf target
git add . && git commit -m "update site" && git push origin gh-pages
```

https://caunu-s.github.io/maven-training/

https://caunu-s.github.io/touch-maven-plugin/

## 最後に

今日では Gradle の選択肢があったり，Java 以外が好まれて使われる状況であったりと Maven を改めて学ぶモチベーションは湧きにくいかもしれません．

一応，Google Trends に聞いてみて「まだ Maven も関心は持たれてるのかなぁ」と思ったりはします．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1997806/58292322-3d81-2f4a-ae79-ff2383ca4b8e.png)

入門レベルでいい具合に記事をまとめられた気はするので「Maven はこれだけやっとけばいいや」ぐらいな感じで読んでもらえていたら嬉しいです．