Mavenでビルドする

こんにちは、
だいぶ空いてしまいましたが、
いつも通り気になったことを調べてまとめていきます。

業務で使っているMacがTouchbarなので
escボタン押すのに違和感があります...笑
Touchbar使いづらいと思ってたけど、
慣れるとありかも...(私用買い換えないけど)

翻訳能力皆無なので、
基本DeepL様の訳を貼ります。

参考

https://qiita.com/ASHITSUBO/items/6c2aa8dd55043781c6b4
https://ts0818.hatenablog.com/entry/2019/04/06/185550
https://www.coppermine.jp/note/2018/12/overviewing-maven-pom/
https://www.slideshare.net/kawasima/maven-196821326

Mavenって?


Apache Mavenは、ソフトウェアプロジェクト管理・理解ツールです。プロジェクトオブジェクトモデル(POM)の概念に基づいて、Mavenはプロジェクトのビルド、レポート、ドキュメントを一元的に管理することができます。

公式→DeepLの翻訳(自分では翻訳できない)です。
POM...


プロジェクトオブジェクトモデルはMavenの基本単位であり、プロジェクトの構造や依存関係などを定義します。

この辺りは深く考えないことにします。
(POMファイルってなんだろうってずっと思ってたけどなんとなくわかってよかった)

いつもビルドしてくれてありがとうございます。
中身を全く知らないので少しでも歩み寄っていきます。

理解すべき設定ファイル

プラグインのダウンロードとかはおいといて、
今回は設定ファイルに焦点を当てていこうと思います。

settings.xml

ローカルリポジトリは、インストールしたMavenのディレクトリ(以下、MAVEN_HOME)内の、confにある、「settings.xml」にリポジトリのパスを記述することで設定します。
settings.xmlには、初めからたくさん英語が書いてありますが、ほとんどはコメントです。
そのなかに<localRepository>タグがあるはずなので、そこにリポジトリの設定を記載します。

今回はプロジェクトで利用するので、一旦こちらは触れず...
新規プロジェクトを立ち上げる場合には要設定ですね。

pom.xml

出ました、pom
正直、jarファイルの追記やバージョンの変更くらいしかしたことありません。

<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>

 <!-- The Basics -->
 <groupId>...</groupId>
 <artifactId>...</artifactId>
 <version>...</version>
 <packaging>...</packaging>
 <dependencies>...</dependencies>
 <parent>...</parent>
 <dependencyManagement>...</dependencyManagement>
 <modules>...</modules>
 <properties>...</properties>

 <!-- Build Settings -->
 <build>...</build>
 <reporting>...</reporting>

 <!-- More Project Information -->
 <name>...</name>
 <description>...</description>
 <url>...</url>
 <inceptionYear>...</inceptionYear>
 <licenses>...</licenses>
 <organization>...</organization>
 <developers>...</developers>
 <contributors>...</contributors>

 <!-- Environment Settings -->
 <issueManagement>...</issueManagement>
 <ciManagement>...</ciManagement>
 <mailingLists>...</mailingLists>
 <scm>...</scm>
 <prerequisites>...</prerequisites>
 <repositories>...</repositories>
 <pluginRepositories>...</pluginRepositories>
 <distributionManagement>...</distributionManagement>
 <profiles>...</profiles>
</project>

XML形式で書かれた読みにくいやつです。
dependencyしか編集してないってことです、わたし。

今回はちゃんと各タグみていきます

https://www.techscore.com/tech/Java/ApacheJakarta/Maven/2-2/#maven-2-4
https://maven.apache.org/pom.html#Quick_Overview
こちらを参考に、ありがとうございます。

ルート要素となるprojectタグとバージョンを管理するmodelVersion(基本的には4.0.0)を除いて大きく4つありますね

The Basics

<groupId>:プロジェクトのルートパッケージ
<artifactId>:成果物の名前、jar,warの前につく
<version>:プロジェクトのバージョン(1.0-SNAPSHOT...については後述)
<packaging>:作成する成果物のパッケージング、デフォルトはjar

プロジェクトの完全修飾名は、 <groupId>:<artifactId>:<version>
アーティファクトのバージョン番号について、決まった採番のルールはない(たぶん)
ただし、末尾に -SNAPSHOT とついたバージョンについては、特別な意味がある
-SNAPSHOT プレフィックスが付いたバージョンは、それが開発中のバージョンであることを表している
開発中のバージョンは、アーティファクト(jar)の中身が更新される可能性があることを表している
一方で、 SNAPSHOT が付いていないバージョンは基本的にリリースバージョンを表しており、中身が更新されない(というのがお約束)

<parent>:子プロジェクトの場合は、親を指定する
    <groupId>
     <artifactId>
     <version>
親としたいプロジェクトを一意に

<dependencyManagement>:子プロジェクトに適用したい依存関係はこちらで定義
<dependencies>
    <dependency>で依存関係を定義していく
        <groupId>
        <artifactId>
        <version>
    </dependency>dependenciesの中に複数OK
</dependencies>

子の pom.xml に記述されていない設定は、親の pom.xml の設定が引き継がれることになる
<groupId> や <version> なども親の設定が引き継がれるため、子の pom.xml では記述が不要になる
ただし、 <artifactId> はさすがに指定しなければならない

注意点としてはあくまで親側では宣言のみであるため、
実際使う場合は子側のpomに記載が必要であることですかね。

リポジトリ、依存関係の理解は大事なので後述する

 <modules>

プロジェクトの集約では逆に親が子を参照する形になる
プロジェクトを集約すると、親プロジェクトでまとめてゴールやフェーズを指定して実行できるようになる

ややこしいの出てきた〜
依存関係の方では親で定義したのを子供がみにいく、という形だったが
ここでは親が子供をみにいくのかな〜

プロジェクトを集約するには、集約元となるプロジェクトで <modules> を使用する
<modules> の下に、集約対象となるプロジェクトを <module> で列挙していく
集約元となるプロジェクトの <packaging> は、 pom にしておく必要がある

よさそうですね、WAR作るときにどのjarを取り込むのかを指定する感じですかね

Build Settings

ビルドの詳細設定ができます。

<build>
     <defaultGoal>:実行時に何も指定されていない場合のゴールの設定です。
    <directory>:ビルドがファイルをダンプするディレクトリです。
デフォルトは${basedir}/targetです。
     <finalName>:ビルドされたときのバンドルされたプロジェクトの名前です(拡張子は省いて、例えばmy-project-1.0.jar)。
デフォルトは${artifactId}-${version}です
     <filters>:どのプロパティファイルをみるかの設定です?たぶん。
         <filter>
    </filters>

コンパイルからビルド成果物までをすべて一度で生成するので、goalは「install」を指定します。
goalというのは、Maven実行時に指定するコマンド引数です。
厳密にはきちんとした意味があるのですが、ここでは説明しませんので、そのあたりはきっちりした参考書籍を見て「フェーズ」と「ゴール」を理解してください。

<reporting>:プロジェクト内のどこにリソースが存在するかを指定できます。
さらに下のタグは使う時調べる。(もう疲れた)

More Project Information

もっと詳しく設定ができます。
細かいところはリファレンスで使う時に確認

<name>
<description>
<url>
<inceptionYear>
<licenses>
<organization>
<developers>
<contributors>:

Environment Settings

環境設定、こちらも使う時に...笑

<issueManagement>
<ciManagement>
<mailingLists>
<scm>
<prerequisites>
<repositories>
<pluginRepositories>
<distributionManagement>
<profiles>

リポジトリってなに

Maven の大事な構成要素の1つにリポジトリ(Repository)がある
Maven では、作成したプロジェクトの成果物(アーティファクト)をリポジトリに保存して管理する
アーティファクトの実体は、普通はそのプロジェクトをビルドしてできた jar ファイル

うん、、、

リポジトリには2種類ある
ローカルリポジトリ
リモートリポジトリ

Maven はまず先にローカルリポジトリを検索するようになっている
ローカルリポジトリに目的のアーティファクトが存在しない場合に、リモートリポジトリに検索に行くようになっている
ローカルリポジトリ
リモートリポジトリからダウンロードしてきたアーティファクトやメタ情報(pom.xml とか)は、 Maven を実行したマシンのローカルにキャッシュされる
このキャッシュ先のことを、ローカルリポジトリと呼ぶ
リモートリポジトリ
デフォルトで使用されるリモートリポジトリとして、セントラルリポジトリというものが存在する
セントラルリポジトリには世界中の様々な OSS のアーティファクトが収められてい

リポジトリは2つあって、はじめに定義したときはリモートリポジトリからとってきて、ローカルリポジトリにキャッシュしておく。
基本的にはローカルをみにいってなかったらリモートをみにいくようにすることで時間の短縮やネットワークがない環境でも利用できるようにする

次は依存関係ですね

依存関係ってむずかしそう

pom.xml では、そのプロジェクトが使用するアーティファクトを依存関係として次のように定義できる

このように宣言すると、 Maven はプロジェクトをビルドするときに自動的に <dependencies> で宣言したアーティファクトをリモートリポジトリからダウンロードしてきてくれて、クラスパスに追加してくれる
つまり、ライブラリを手で落としてくる手間が省ける
さらに、リポジトリに格納されたアーティファクトには、それ自身の pom.xml も格納されている
もしプロジェクトで指定したアーティファクトの pom.xml に、さらに <dependencies> があった場合、 Maven はその依存アーティファクトも自動的にダウンロードしてくれる
つまり、芋づる式にすべての依存関係を解決してくれる仕組みになっている

ありがとうございますありがとうございます。
外部ライブラリが呼んでいる外部ライブラリもとってきてくれるの
素晴らしすぎですよね。

ここでずっと気になっているところを解決したいです
同じライブラリを複数バージョン指定されているときってどうなるの

少なくともこれまででわかっていることは、
親pomでバージョンを指定していても子pomで別バージョンを指定すれば
子では子バージョンが読み込まれるはず、ということです。

なので、複数moduleを持つ場合でも、
各moduleごとに指定したバージョンのライブラリを利用できる
という認識でよさそうですかね。

さて、ではmoduleの中で
dependencyで直接したバージョンと呼び出した外部ライブラリがさらに呼び出したバージョンが異なる場合はどうなるのでしょう

だめだ、調べたけど解決しない...
わかる方がいましたら教えていただきたいです...

依存関係の競合

プロジェクトA(BおよびCに依存しています)
プロジェクトB(Dに依存しています)
プロジェクトC(Dに依存しています)
プロジェクトD

この場合、解決Dへのパスの深さは同じです。起こることは、Mavenが競合することです。 Mavenに依存関係を除外するように伝えることができることは知っています。しかし、私の質問は、そのような種類の問題にどう対処するかです。実際のアプリケーションでは、多くの依存関係があり、場合によっては多くの競合もあります。

ものを除外するのが本当にベストプラクティスの解決策ですか、またはこれに対する他の可能な解決策はありますか?一部のバージョンが変更され、Mavenが別の依存関係を取得したため、突然ClassNotFound例外が発生した場合、対処が非常に困難であることがわかりました。この事実を知っているため、問題が依存関係の競合であると推測するのが少し簡単になります。

これがおそろしいんですよね...
Aで使いたいライブラリのバージョンと、
Bで使いたいライブラリのバージョンが異なる時、どうしたらいいのでしょう...

Mavenは、競合することなく両方の状況を処理できます。推移的な依存関係の2つのバージョンが必要な場合、競合が発生します。 ClassNotFoundExceptionは、実際に使用される競合する依存関係のバージョンでは使用できないクラスを使用しようとするアプリ(または依存関係)からの結果を記述します。問題を解決する方法は複数あります。
・競合する依存関係に依存する使用中のライブラリのバージョンを更新し、それらがすべてその依存関係の同じバージョンバージョンに依存するようにします
競合する依存関係を、含めるプロジェクトの直接的な依存関係として宣言します(この例では、欠落しているクラスが含まれているバージョン)。
・POMの<dependencyManagement>セクションを介して、推移的依存関係が使用する競合する依存関係のバージョンを指定します
・<exclusion>を使用して、競合する依存関係の不要なバージョンを、それらに依存する依存関係に含めることから明示的に除外します。

本来であればバージョンを合わせてあげるのがいいのだけど、
対応としてはこんな感じなのかな...

Aのpomで使いたいライブラリのバージョンを指定、
Bのpomで使いたいライブラリのバージョンを指定でやりたいことはできそう?

これは基本的にMavenの問題ではなく、Javaの問題です。プロジェクトBとプロジェクトCにプロジェクトDの2つの互換性のないバージョンが必要な場合、プロジェクトAで両方を使用することはできません.
残念ながら、これらのような競合を解決するMavenの方法は、ご存じのとおり、除外する競合を選択することです。

mvn dependency:analyzeおよびmvn dependency:treeを使用すると、競合の検出に役立ちます。

そうなんです、、、わかっています。。。
競合の検出の確認はやっておきます。

あとがき

前職時代から触れる環境にあったのに
関わってこなかったのを後悔しました。

細かいタグは使うときに調べるスタンスがよさそう

この記事が気に入ったらサポートをしてみませんか?