20200407のJavaに関する記事は9件です。

#1 世知辛い初心者エンジニアの壁 ~エンジニアになりたい君へ~

初投稿になります。あつぎです?

未経験新卒で入社したものの、初っ端から自宅学習を強いられております。
「あっ、これ前にやったやつだ!」みたいなこともなく、事前に自分で勉強していたjavaやマークアップ言語、Web系言語も意味をなしておりません。





まず初心者の方に伝えたいのは、入社する会社が
【SIer】なのか【Web系】なのか知っておきましょう。
なんとなく開発したいは、悪くもないですが、良くもないです。はい。

■C言語めちゃくちゃムズイ

題名の通りです。
研修でC言語を学ぶのは「普通」なのでしょうか?
挫折率の高い言語を研修にするのは、なんとも言えない気持ちです。

開発環境はEclipse IDE(統合開発環境)で作業してたのですが、
Javaのようにサクサク実行ができず詰んでおります:scream:


■詰んでるところ

プログラミングというのは、


❶コードを書いて
❷コンパイルして(PCが読める言葉に翻訳して)
❸実行結果(画面に表示される)
のですが、Eclipseの環境だと、なぜかコンパイルできずに死亡しております…。


配布資料を読んでも、バイナリー作成されず撃沈。
Windowsなのでコマンドプロンプトで実行するも、Eclipseで動かなきゃ意味がなし。
今妥協してvisualStudio(別の開発環境)でしてみるつもりです。


同じく、EclipseでC言語、ビルドしてもバイナリーできないよ!って躓く初心者いませんかね。
私はいったん放置して、別の手段で試してみます。



では、またの

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ウイルス性肺炎と診断されたエンジニアの約2か月の在宅勤務記。

序:某渡辺さんの手記を読んだ。

 4月1日実名公開の『これで「軽症」と言うのか。新型コロナ感染で入院中、渡辺一誠さんの手記』を読んだ。もちろん存じ上げない方だが六本木勤務らしい。いろいろと辛そう。。緊急宣言が出されることで、IT業界の方々も、これからいろいろな影響を受けることがあることだろう。
 ということで、参考になればと、参考情報として、2月下旬に自室で悶絶(もんぜつ)しつつも保健所で門前払いされたのち3週間後に、自費で受けたCTスキャンでウイルス性肺炎と診断された私(中年♂)のこの二月ほどを振り返るポエムを書くことにした。恥ずかしいのだけれども。。
 状況としては、病状が悪かったはじめの1月は在宅でいろいろとやらかした。回復後の1月弱で、いくつかを取り戻せたと思っている。勤怠については、(理解ある勤務先のおかげで)2か月間皆勤とさせてもらっている。

(付記) CTによる診断と血液検査

(その他の検査での陽性反応を経て別途の投薬を受けるも)発熱してから1月たっても呼吸音がおかしいとのことで(通院4先目の病院で)CTスキャンをしてもらったところ、ウイルスが住み着く部位(以下のようなところらしい)に肺炎が残っているとのことで、ウイルス性肺炎と診断される(以下のような断面図を何枚か見ながら説明を受ける)。 => 免疫系のフルスキャン版の血液検査をするから、支払いヨロピクね、と、言われ、採血される。

Image.jpeg
=> 医師から受けたアドバイスは割合と有り難かった。...結果、お金は減ったが、現在は完治と思われ。

今、なんとなく具合が悪い人へ。

...緊急なんちゃら宣言が出た今となってては、疑わしい体調であるとしても、コロナの検査に向かう際に業務が継続できる装備(PC/関連書籍等)を持参した上で、病院・保健所等へと向かうことをお勧めする。いったん検査に至るかもしれないラインと入ると、途中離脱は困難かと。

はじまり

中年のデータエンジニアである私は、2月中旬に軽く発熱した。その後、数日在宅で過ごすことに。だいたいは布団にくるまり業務継続。毎日たらふく寝た後、遅れを取り戻そうと思っていた週末にさらに症状が悪化。日曜日にこれはまずいと思い、布団から「コロナ」と検索しその界隈に電話するも、よほどのことがない限りコロナのPCR検査をしてもらえない状況を知る。週明けからは電話自体がつながらず。...以下、委細略するが、動けるようになった後は、若干の発熱と逆の低体温に苦しむ状態が続く(要するに起きている間、たいてい、寒い)。あと、咳がないため、国の定める基準に微妙に届かず。

年度末のリリースを前にいくつかやらかした件と、反省記。

① デグレでリリース遅延

 体調はさておき、リリース予定は近づいている。一日に3時間くらいしか働かない頭で、残タスクをこなしつつ、単体テストで出たバグや指摘事項を直していく...体調が回復してから半月弱でリリースが迫る。受入環境でのデモ作成時で、バリデーション周りのデグレに気がつく。。。
 既にギリギリのタイミングであったため、平謝りし、リリース時期を週明けまで延期してもらう。落ち着いてみると、やらかしてました、私。

原因は、長年の私の癖。古にJavaを使っていた時代から、入力に対し、複雑なバリデーションをして、かつ、エラーレポートを出す際には、出てきたエラーは、コレクションにまるっと入れることにしていた。
つまり、バリデーション関数の返り値は、scalaの擬似コードとしてシーケンス(Seq)で書けば、

ret = Seq("エラー理由1","エラー理由2"...)

といった風であり、Seqが空であれば、エラーなしとなる。はい、私は、ret.length == 0でチェックするのが癖なのでした(もちろん、バリデーション関数側では、retがnullとはならない手当てをしている)。

平謝りした翌朝、バリデーション周りのクソ長いコードをおそるおそる見てみると、ret.length == 0だったはずのコードの、一箇所が、ret == 0となっているではないですか。。。scalaではこの判定はコンパイルエラーとならず、常にfalseとなる。...デグレるわけだわ。
...私の同僚のコーダーには日本人はいないので、デグレ報告は、degradationではなく、どことなくregret(後悔)が頭に浮かぶregressionとなる。委細を書くのも恥ずかしく、silly regression(愚かなデグレ)としてレポート報告することに。その後、遅まきながら、コレクションに対し、xxx.lentgh == 0と書いていたコードは、xxx.isEmptyと逐次置換したのでした。
=> qiita上にも、このあたりの記述はいろいろあります。気になる方は、`java list isempty"あたりでググりませう。

教訓1

 病気の時は、さまざまな能力が衰える。そうなった時になんとかなるように、手抜きをせず、安全に振ったコーディングを習慣づけるように。型安全とかnull安全とか、どこかの記事を読んで終わりにせずに、自身の日々書くにコードを安全に振ることを続けていくのが吉。

② ヒアリングできないことを理由に、無駄な実装を行う。

 私は、ラージスケールスクラム(LSS)チームに属している。セキュリティが大事であるため、データセンターは具体的にどこに在るかは知らない。ただ、サーバはアメリカの東の方の標準時で管理されている。納期が迫る中、データは規約に従いUTCのタイムスタンプで保存していたのだが、ユーザー向けのログはどの時刻帯で確認してもらうのだったか...?
 私のチームのステークホルダーは日本人であるため、問い合わせるのだが、回答がない。どうやら、インフルに罹患されてしまったとのこと。時間もないし、出力は、UTCでもJSTでも出せるようにしておこう...と思ったが、提供されているライブラリにJSTへの変換は存在しない。。。むむ~、とりありず、JSTへの変換ロジックを追加...しようと思うのだが、規約で強制されているライブラリは継承が許されていない(すまんね、テヘペロ的なコメントだけがある)。大急ぎで、アメリカの東に方に問い合わせるのだが回答は帰ってこずに(あ~、頭痛いのに英語辛い)さらに開発サーバが応答しなくなり放置状態に(アメリカてコロナで云々が報じられていたころの週末だった...)。
 むむ~、これはまずい、なんとかせねば、とサーバ側の設定がことなる環境でJSTへの作成ロジックを念の為、作成。ローカルで走らせたが、その後にテスト環境で走らせたが、意図通り動作せず。...コードは恥ずかしいので記さない。...かなり焦っていたところ、ステークホルダーより連絡があり、そもそもJSTへの変換は必要ないことを知る。...結果、無駄に時間と体力を失い、①のデグレ修正に間に合わず。

教訓2

焦っている時ほど、平常心という奴ですね。あなたのその実装、本当に求められているのですか、と第三者的に問える己が欲しい。手を焦って動かす前に。

③ (私生活)熱にうなされたまま、リスクある投資を続け...

委細恥ずかしいので省きますが、平熱に戻る10日ほどの間、コードを書く気力もないまま、気分転換にした、短期投資で、とても高い確率で失敗し、3桁万円の損を発生させました。。。肺よりも心が痛む。。

教訓3

コードを書けないからといって焦るな。焦って、溶かしたお金は戻らないが、焦りは倍以上になって戻ってくるぞ。。

救い

2ヶ月間在宅で皆勤賞なので、損失をぎりぎり上回る給与はいただけているので、生きてはいけます。

回復してからしたこと。

① 業務復帰

当然、すべきことは、デプロイとリリース。3日(+週末分)、遅れましたが。。。

② 焦る心のケア

 なんの病気にせよ、けっこう長く寝込んだあとに回復できた後は、たぶんかなりの確率で眠れない夜がある。だって身体の方は永らく寝過ぎているのですから。そのことに気がついてからは、定時後は、今後のキャリアアップのための勉強をすることにした。...別にキャリアアップを心がけなくてもいいのですが、回復期には、平常心を取り戻すために何をすべきかを考えるべき。
 元アプリケーションエンジニア(一時、webエンジニア)にして、最近は主にsparkを使うデータエンジニアである私。もう良いお歳なのだが、当然、周りからはDevOpsしろよと、言われている。...が、残念ながら、(特殊事情のJenkinsも)ansibleもterraformも(その他Hashicorp stackも)苦手意識があった。
 眠れない夜に、とはいえ、そこそこのお金を頂きながらこれらに食いついてきたのでから、と振り返り、改めて、webのエッジノードから裏方の機械学習までの範囲を担えるMlOpsなエンジニアになるには何が必要かを考え、自身のpros-consを作ってちょっとでも弱点を減らそうとする。
...主には、弱点(cons)の補いだが。

  • 裏方のちょっとしたツール: Goでシングルバイナリ作るのがいいんだろうなぁ...golangは強制されないと書きたくない...気分転換にvlangでも書いておくか。
  • 後に作る何かを作っておくか: (フロントエンド)+(エッジノード)+graphql+(バックエンド)かな。
  • フロントエンド: 学ぶのが多いのは嫌なので、pureJSでいけるSvelteに極振り...したいが、スケールした際にはelmなのだなと再確認。
  • エッジノード: SSR時代だしnodejs界隈を知っておこう。typescriptだと抽象度が高いnestなるのがあるのか...認証周りはfirebaseに任せるとすると
  • バックエンド: neo4jは中も少し見たしなんとなりそう。が、graphqlと組み合わせるならば、間に枯れたRDBMSを噛ませるのが安全...となると、Postgresでgraphqlする、これかな...

...などなど、結局、MLOps周りは対して学習が進んでいない気もするがあまり気にしない(=> 現在進行形、そのうち何か書く)。意識して日常を取り戻すことが大事。

寝付けない夜には。

自身の今の職場とか、やらかしたことなどをいったん離れて、他方で、(確たる宗教心がない方には)謎な何かにすがってもたぶんいいことないので、自身の職業倫理と立ち位置を振り返るのが良いと思う。ITエンジニアである場合は、当然、エンジニアとしての自身の今後を見つめ直す機会だ。

今後に向けて。

 こんな風に迷走したのだが、それぞれの時刻帯で24時間が続く開発チームらしく、会社にいようがどこにいようが開発もデプロイもできることは、やはりありがたかった。日中寝込んでいても、深夜とか早朝にコードをデプロイできるしね。

ちょい付言。

 結果的にチームに救われた立場なのではあるのだが、チーム内に日本人エンジニアがいないのは寂しかった。ということで、最近のあんなこんなで在宅勤務に困難を抱えているなど、twitter界隈などで絡んでいただけるとちょっとうれしいかも。また、国内でSESな案件などをこなしていたエンジニアの方で、Scala/Javaの腕に覚えのある方は我々のチームに加わっていただける方はいないだろうか。外資だが、英語あんまりできなくても、コードが読めればなんとかなるよぅ。チームから人集めを頼まれ、人材エージェントにもお声がけしているのだが、Scala ∩ 英語では、ここまで日本人エンジニアの方にはヒットせず。...せっかく日本人エンジニアの方々にけっこう定着した技術メディアであるqiitaさんなんだからこうしたエンジニア人材問題、ビジネスとしてなんとかしてくれないかなと思ったり。あ、自分でサービス作ってみようかなと妄想してみたり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQL に UUID のデータを 16 byte で保存する

似たような話は各所にあるのですが、自分の理解向け、備忘録向けに書きます。

UUID について

https://ja.wikipedia.org/wiki/UUID

もとの起こりはWikipediaなどを見るとして、実運用的には開発時にデータに一意なIDを割り振る際によく用いられる。

UUIDには複数のバージョンがあり、最も開発で用いられていると思われるのは UUIDバージョン4(ランダム生成) 、その他に バージョン1(MacアドレスやTimestamp情報を用いて生成) などがある。

ビット幅は 128ビット(16バイト) で、文字列表現では16進法で以下のような形で表現する。
(32バイト+バウンダリ4バイト=36バイト。 "-" がバウンダリ文字列)

550e8400-e29b-41d4-a716-446655440000

SQL を用いて 16バイトで MySQL に保存する (8.0 以前 )

MySQL に16バイトで保存する場合には、16進数の文字列表現のUUIDからバウンダリ文字列を取り除いたものを、Binary に変換して保存する、という流れになります。
それを SQL で表現すると以下のような形になります。

CREATE TABLE `sada ` (
  `id` BIGINT NOT NULL AUTO_INCREMENT,
  `UUID VARBINARY(16) NOT NULL,
  `name` VARCHAR(1024) NOT NULL,
  PRIMARY KEY (`id`)
)
  ENGINE = InnoDB,
  CHARACTER SET = `utf8mb4`, COLLATE = `utf8mb4_bin`;
INSERT INTO sada (
    uuid,
    name
)
VALUES(
    UNHEX(REPLACE('550e8400-e29b-41d4-a716-446655440000', '-', '')),
    'masashi'
);

SQL を用いて 16バイトで MySQL に保存する (8.0 以後 )

MySQL 8 以降から、UUID を操作するための関数が追加されています。
8 以降を使用している場合はこれを使うとオレオレ感を感じる独自処理から脱却することができます。

https://mysqlserverteam.com/mysql-8-0-uuid-support/

  • UUID_TO_BIN (UUID文字列をBinaryに変換)
  • BIN_TO_UUID (BinaryをUUID文字列に変換)

例は上記公式ブログを引用します。

CREATE TABLE t (id binary(16) PRIMARY KEY);

INSERT INTO t VALUES(UUID_TO_BIN(UUID()));
Query OK, 1 row affected (0,00 sec)

#a few inserts later..

SELECT BIN_TO_UUID(id) FROM t;
+--------------------------------------+
| BIN_TO_UUID(id); |
+--------------------------------------+
| 586bcc2d-9a96-11e6-852c-4439c456d444 |
| 5942e49a-9a96-11e6-852c-4439c456d444 |
| 841ae775-9a96-11e6-852c-4439c456d444 |
| 84393c8e-9a96-11e6-852c-4439c456d444 |
| af0f27e2-9aad-11e6-852c-4439c456d444 |
+--------------------------------------+
5 rows in set (0,00 sec)

Java / Kotlin を用いて 16バイトで MySQL に保存する

プログラムからも、各言語で生成した UUID 情報を Binary することで 16 バイトにおさめて保存することが出来ます。

ここでは Kotlin の例のみを記載します。
Java / Kotlin には UUID クラスが存在するので、以下のように UUID型 -> Byte 配列、ならびに Byte 配列 -> UUID型 の変換ロジックを作成し、使用する環境(ORM等)にあわせて組み込めば OK です。

    fun uuidToBytes(uuid: UUID): ByteArray {
        val buffer = ByteBuffer.allocate(16)
        buffer.putLong(uuid.mostSignificantBits)
        buffer.putLong(uuid.leastSignificantBits)
        return buffer.array()
    }

    fun bytesToUuid(bytes: ByteArray?): UUID? {
        if (bytes == null) {
            return null
        }
        val buffer = ByteBuffer.wrap(bytes)
        return UUID(buffer.long, buffer.long)
    }

Appendix. より省サイズの ID 表現

UUID は 16 バイトもあるかなり大きめな ID 表現となるので、 UUID の保存で省サイズ化を頑張るより、より省サイズの ID を採用するという手もあると思います。
以下は参考文献程度に。

軽量なTime-based ID生成器”shakeflake(仮称)”について

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jersey+Spring FrameworkでRESTfulAPIの最小構成サンプル

こちらの記事の続きです。

Mavenでマルチモジュール構成にする(Jersey RESTful)
https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b

目的

JserseySpring Framework(not Boot)でRESTfulAPIを実装する。

Java Springで調べるとSpring Bootの例ばかりで、多分Boot使ったほうがいろいろ楽なんでしょうが、大人の事情で使えない(かもしれない)ので、Spring Frameworkの方を使ったサンプルを目指します。

ゴール

Jersey+Spring Frameworkの最小構成のプログラムでHello World的なものが動く。

環境など

ツールなど バージョンなど
MacbookPro macOS Mojave 10.14.5
IntelliJ IDEA Ultimate 2019.3.3
Java AdoptOpenJDK 11
apache maven 3.6.3
Jersey 2.30.1
JUnit 5.6.0
Tomcat apache-tomcat-8.5.51
Postman 7.19.1
Spring Framework 5.2.4-RELEASE

参考サイト

自分が作ったJerseyのプロジェクトや、Spring MVCのHelloWorldプロジェクトに追加しようとしたのですが、情報を探すとどうしてもSpringBootを使っているものが多く、なかなか難儀していました。

やっと見つけたのがこちらのサイトです。

Jersey + Spring integration example
https://mkyong.com/webservices/jax-rs/jersey-spring-integration-example/

十年近く前の情報で、かなり古いですが、上がっているプロジェクトは一応そのまま動きました。(※ただしJDK9以降の環境では、JAXB関連の依存関係の追加が必要)
なので、いったんそこから最新版に上げることを目指し、成功したので、その最終形態をメモしておきます。

プロジェクト設定手順

1.Mavenプロジェクトを新規作成

IntelliJ IDEAで新規にMavenプロジェクトを作成します。
手順はこちらなどを参考にしてください。

2.依存関係を設定

pom.xmlは次のようになりました。
参考サイトと変わっているのは、各バージョンが最新版になっていることと、それに伴いパッケージ移動したものを変更しています。また、JAXB関連がJDK9以降削除されているのでその依存関係も追加しています。

pom.xml
<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>my.example.jerseyspring</groupId>
    <artifactId>RESTfulExample</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>RESTfulExample Maven Webapp</name>
    <url>http://maven.apache.org</url>

    <dependencies>

        <!-- Jersey -->
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server -->
        <dependency>
            <groupId>org.glassfish.jersey.core</groupId>
            <artifactId>jersey-server</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>${jersey.version}</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!-- Spring dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- Jersey + Spring -->
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.ext/jersey-spring5 -->
        <dependency>
            <groupId>org.glassfish.jersey.ext</groupId>
            <artifactId>jersey-spring5</artifactId>
            <version>${jersey.version}</version>
        </dependency>

        <!-- JAXBはJDK9から外されました -->
        <!-- https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.2</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>RESTfulExample</finalName>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <inherited>true</inherited>
                <configuration>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <properties>
        <spring.version>5.2.4.RELEASE</spring.version>
        <jersey.version>2.30.1</jersey.version>
        <junit.jupiter.version>5.6.0</junit.jupiter.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
</project>

3.トランザクションクラスを作成

Beanでインジェクトするクラスとなります。
インターフェースを作ってImplementするのが作法みたいですが、そうなっていなくても問題はないです。Springの売りの1つがDI(依存成注入)なので、後でテストするときにモックすることなどを考えたら、最初からやっておいたほうがいいでしょうね。

インターフェースクラス

transaction/TransactionBo.java
public interface TransactionBo {
    String save();
}

実装クラス

transaction/impl/TransactionBoImpl.java
public class TransactionBoImpl implements TransactionBo {
    public String save() {
        return "Jersey + Spring example";
    }
}

4.サービスクラスを作成

TransactionBoImplをSpringにDIしてもらいますが、@Autowiredアノテーションは使いません。
コンストラクタインジェクションを使います。
(コンストラクタインジェクションが推奨のようです。詳しくは末尾の参考サイトから)

rest/PaymentService.java
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.springframework.stereotype.Component;
import my.example.jerseyspring.transaction.TransactionBo;

@Component
@Path("/payment")
public class PaymentService {

    final TransactionBo transactionBo;

    public PaymentService(TransactionBo transactionBo) {
        this.transactionBo = transactionBo;
    }

    @GET
    @Path("/mkyong")
    public Response savePayment() {
        String result = transactionBo.save();
        return Response.status(200).entity(result).build();
    }
}

5.applicationContext.xml

Beanのクラスをインジェクトをしてもらうために設定するファイルです。
src/main/resourcesフォルダ下に置きます。
パッケージ名とBeanのクラスは自分が作成したものに適宜変更して下さい。

applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="my.example.jerseyspring" />

    <bean id="transactionBo" class="my.example.jerseyspring.transaction.impl.TransactionBoImpl" />

</beans>

5. web.xml

src/main/webapp/WEB-INF/下にweb.xmlを作成して以下のようにします。

web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <display-name>Restful Web Application</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>jersey-serlvet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>my.example.jerseyspring</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>jersey-serlvet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

</web-app>

参考サイトと違うのは、<servlet-class>がJerseyのクラスになっている点くらいです。
あとパッケージ名も自分が作成したものに変えてくださいね。

6.index.jsp

webappフォルダ下に置きます。
なくてもいいんですが、ないとTomcatにデプロイして起動したとき404ページが表示されて気持ち悪いので(^^;

index.jsp
<html>
<body>
    <h2>Jersey + Spring RESTful Web Application!</h2>
    <p><a href="rest/payment/mkyong">Jersey resource</a>
</body>
</html>

実行

Tomcatで実行設定をして起動すれば、次のような画面が表示されるはずです。

jersey_spring_1.png

Jersey Resourceのリンクをクリックすると、rest/payment/mkyongGETメソッドが叩かれ、戻り値が表示されます。

jersery_spring_2.png

curlやPostmanでも成功するはずです。

テスト

せっかくなのでテストも書いてみます。

1.単純なテスト

(1)依存関係の追加

テスト用の依存関係を追加します。
この記事の時と同じにしておきました。

まず<build>/<plugins>下にプラグインを追加します。

pom.xml
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <additionalClasspathElements>
                        <additionalClasspathElement>src/test/java/</additionalClasspathElement>
                    </additionalClasspathElements>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.7.1</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>3.0.0-M4</version>
            </plugin>

続いて、<dependencies>に追加します。

pom.xml
       <!-- テスト -->
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.glassfish.jersey.test-framework.providers/jersey-test-framework-provider-grizzly2 -->
        <dependency>
            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
            <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
            <version>2.30.1</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.assertj/assertj-core -->
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.15.0</version>
            <scope>test</scope>
        </dependency>

(2)テストクラスの実装

基本的にはJerseyの基本サンプルでやったをそのまま踏襲するだけです。

src/test/java/my/example/jerseysample/PaymentServiceTest.java
package my.example.jerseyspring.rest;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import static org.assertj.core.api.Assertions.assertThat;

class PaymentServiceTest extends JerseyTest {

    @Override
    protected Application configure() {
        return new ResourceConfig(PaymentService.class);
    }

    @BeforeEach
    @Override
    public void setUp() throws Exception {
        super.setUp();
    }

    @AfterEach
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @Test
    public void get(){
        final Response response = target("/payment/mkyong").request().get();

        String content = response.readEntity(String.class);
        assertThat(content).isEqualTo("Jersey + Spring example");
    }
}

2.TransactionBoの実装を変えてみる

せっかくなのでSpringのDIを使ってモックに入れ替えられないか試してみました。

(1)依存関係の追加

SpringのDIを動かすために、SpringのTestフレームワークを入れます。

pom.xml
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>

(2)モックBeanクラスを作成

src/test/java/my/example/transaction/impl下に、下記のクラスを作ります。

TransactionBoMock.java
public class TransactionBoMock implements TransactionBo {
    public String save() {
        return "This is mock.";
    }
}

これをBean定義しますが、テスト用なのでテスト用のapplicationContext.xmlを作ります。
src/test/resources下に置きます。

src/test/resources/applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="my.example.jerseyspring"/>

    <bean id="transactionBo" class="my.example.jerseyspring.transaction.impl.TransactionBoMock"/>

</beans>

実装クラスをTransactionBoMockに変更しています。

(3)テストクラスの修正

Junit5に対応した書き方にしています。

PaymentServiceTest.java
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:/applicationContext.xml")
class PaymentServiceTest extends JerseyTest {

2つのアノテーションを追加するだけ!
実行すると、まだ比較文字列を変えていないので、エラーになると思います。

org.opentest4j.AssertionFailedError: 
Expecting:
 <"This is mock.">
to be equal to:
 <"Jersey + Spring example">
but was not.
Expected :Jersey + Spring example
Actual   :This is mock.
assertThat(content).isEqualTo("Jersey + Spring example");

上記の文字列を"This is mock."に変えてやれば完了ですね。

感想

ここにたどり着くまでにかなり時間かかっていますが・・・

出来上がってみれば、割と単純です。
ここまでシンプルになると、DIしてもらうのにapplicationContext.xmlでの定義が必要なんだなとか、そういうことが分かりやすくなりますね。

Spring MVCを入れるとなると、Controllerとかdispatcherとかの設定がまた大変そうですが、RESTfulAPIだけの予定なので、いったんはこれでよいでしょう。(あまりSpring MVCは勉強する気がなかったりw)

次は、もうちょっとちゃんとしたAPIを作って、できればJerseyのみでマルチモジュール化したプロジェクトとマージしていきたいですね。

ここまでのプロジェクトは、以下にアップしてあります。

https://github.com/le-kamba/spring-jersey-sample/tree/simple_base

その他の参考サイト

情報が古かったりBoot向けだったりで、直接の参考にはならなかったけど、細かいところでは色々とヒントを貰ったサイトです。

REST API with Jersey and Spring
https://www.baeldung.com/jersey-rest-api-with-spring

Programmers: Jersey with a Side of Spring
http://pilotprogrammer.com/archive/2019/01/programmers-jersey-with-a-side-of-spring/

SpringでField InjectionよりConstructor Injectionが推奨される理由
http://pppurple.hatenablog.com/entry/2016/12/29/233141

JUnitでテストするときにも、DIが動作するようにするには
https://wikiwiki.jp/webapp/Spring/JUnit

JUnit5 @RunWith
https://www.baeldung.com/junit-5-runwith

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

XMLDecoder反序列化分析

简介

XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包wls-wastwls9_async_response war_async使用了该类进行反序列化操作,出现了了多个RCE漏洞。

本文不会讲解weblogic的xml相关的洞,只是分析下Java中xml反序列化的流程,采用JDK2U21。

什么是SAX

SAX全称为Simple API for XML,在Java中有两种原生解析xml的方式,分别是SAX和DOM。两者区别在于:
1. Dom解析功能强大,可增删改查,操作时会将xml文档以文档对象的方式读取到内存中,因此适用于小文档
2. Sax解析是从头到尾逐行逐个元素读取内容,修改较为不便,但适用于只读的大文档

SAX采用事件驱动的形式来解析xml文档,简单来讲就是触发了事件就去做事件对应的回调方法。

在SAX中,读取到文档开头、结尾,元素的开头和结尾以及编码转换等操作时会触发一些回调方法,你可以在这些回调方法中进行相应事件处理:

  • startDocument()
  • endDocument()
  • startElement()
  • endElement()
  • characters()

自己实现一个基于SAX的解析可以帮我们更好的理解XMLDecoder

package com.xml.java;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.File;

public class DemoHandler extends DefaultHandler {
    public static void main(String[] args) {
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
        try {
            SAXParser parser = saxParserFactory.newSAXParser();
            DemoHandler dh = new DemoHandler();
            String path = "src/main/resources/calc.xml";
            File file = new File(path);
            parser.parse(file, dh);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        System.out.println("characters()");
        super.characters(ch, start, length);
    }

    @Override
    public void startDocument() throws SAXException {
        System.out.println("startDocument()");
        super.startDocument();
    }

    @Override
    public void endDocument() throws SAXException {
        System.out.println("endDocument()");
        super.endDocument();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        System.out.println("startElement()");
        for (int i = 0; i < attributes.getLength(); i++) {
            // getQName()是获取属性名称,
            System.out.print(attributes.getQName(i) + "=\"" + attributes.getValue(i) + "\"\n");
        }
        super.startElement(uri, localName, qName, attributes);
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        System.out.println("endElement()");
        System.out.println(uri + localName + qName);
        super.endElement(uri, localName, qName);
    }
}

输出了

startDocument()
startElement()
characters()
startElement()
class="java.lang.ProcessBuilder"
characters()
startElement()
class="java.lang.String"
length="1"
characters()
startElement()
index="0"
characters()
startElement()
characters()
endElement()
string
characters()
endElement()
void
characters()
endElement()
array
characters()
startElement()
method="start"
endElement()
void
characters()
endElement()
object
characters()
endElement()
java
endDocument()

可以看到,我们通过继承SAX的DefaultHandler类,重写其事件方法,就能拿到XML对应的节点、属性和值。那么XMLDecoder也是基于SAX实现的xml解析,不过他拿到节点、属性、值之后通过Expression创建对象及调用方法。接下来我们就来分析下XMLDecoder将XML解析为对象的过程。

XMLDecoder反序列化分析

所有的xml处理代码均在com.sun.beans.decoder包下。先弹一个计算器

<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="1" >
            <void index="0">
                <string>calc</string>
            </void>
        </array>
        <void method="start"/>
    </object>
</java>
package com.xml.java;

import java.beans.XMLDecoder;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Main {
    public static void main(String[] args) {
        String path = "src/main/resources/calc.xml";
        File file = new File(path);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        BufferedInputStream bis = new BufferedInputStream(fis);
        XMLDecoder xmlDecoder = new XMLDecoder(bis);
        xmlDecoder.readObject();
        xmlDecoder.close();
    }
}

运行弹出计算器,在java.lang.ProcessBuilder#start打断点,堆栈如下:

start:1006, ProcessBuilder (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invoke:75, Trampoline (sun.reflect.misc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:57, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:601, Method (java.lang.reflect)
invoke:279, MethodUtil (sun.reflect.misc)
invokeInternal:292, Statement (java.beans)
access$000:58, Statement (java.beans)
run:185, Statement$2 (java.beans)
doPrivileged:-1, AccessController (java.security)
invoke:182, Statement (java.beans)
getValue:153, Expression (java.beans)
getValueObject:166, ObjectElementHandler (com.sun.beans.decoder)
getValueObject:123, NewElementHandler (com.sun.beans.decoder)
endElement:169, ElementHandler (com.sun.beans.decoder)
endElement:309, DocumentHandler (com.sun.beans.decoder)
endElement:606, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
emptyElement:183, AbstractXMLDocumentParser (com.sun.org.apache.xerces.internal.parsers)
scanStartElement:1303, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:2717, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:607, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:489, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:835, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:764, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:123, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:1210, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:568, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:302, SAXParserImpl (com.sun.org.apache.xerces.internal.jaxp)
run:366, DocumentHandler$1 (com.sun.beans.decoder)
run:363, DocumentHandler$1 (com.sun.beans.decoder)
doPrivileged:-1, AccessController (java.security)
doIntersectionPrivilege:76, ProtectionDomain$1 (java.security)
parse:363, DocumentHandler (com.sun.beans.decoder)
run:201, XMLDecoder$1 (java.beans)
run:199, XMLDecoder$1 (java.beans)
doPrivileged:-1, AccessController (java.security)
parsingComplete:199, XMLDecoder (java.beans)
readObject:250, XMLDecoder (java.beans)
main:21, Main (com.xml.java)

XMLDecoder跟进readObject()
image.png

跟进parsingComplete()
image.png

其中this.handlerDocumentHandler
image.png

到这里进入com.sun.beans.decoder.DocumentHandler#parse

image.png

圈住的代码其实和我们写的DemoHandler里一模一样,通过SAXParserFactory工厂创建了实例,进而newSAXParser拿到SAX解析器,调用parse解析,那么接下来解析的过程,我们只需要关注DocumentHandler的几个事件函数就行了。

DocumentHandler的构造函数中指定了可用的标签类型
image.png

对应了com.sun.beans.decoder包中的几个类
image.png

在startElement中首先解析java标签,然后设置Owner和Parent。
image.png

this.getElementHandler(var3)对应的就是从构造方法中放入this.handlers的hashmap取出对应的值,如果不是构造方法中的标签,会抛出异常。
image.png

然后解析object标签,拿到属性之后通过addAttribute()设置属性
image.png

在addAttribute()没有对class属性进行处理,抛给了父类com.sun.beans.decoder.NewElementHandler#addAttribute
image.png

会通过findClass()去寻找java.lang.ProcessBuilder
image.png

通过classloader寻找类赋值给type
image.png

赋值完之后跳出for循环进入this.handler.startElement(),不满足条件。
image.png

接下来解析array标签,同样使用addAttribute对属性赋值
image.png

同样抛给父类com.sun.beans.decoder.NewElementHandler#addAttribute处理
image.png

继续抛给父类com.sun.beans.decoder.NewElementHandler#addAttribute
image.png

接下来继续设置length属性
image.png

最后进入com.sun.beans.decoder.ArrayElementHandler#startElement
image.png

因为ArrayElementHandler类没有0个参数的getValueObject()重载方法,但是它继承了NewElementHandler,所以调用com.sun.beans.decoder.NewElementHandler#getValueObject()
image.png

这个getValueObject重新调用ArrayElementHandler#getValueObject两个参数的重载方法
image.png

ValueObjectImpl.create(Array.newInstance(var1, this.length))创建了长度为1、类型为String的数组并返回,到此处理完array标签。

接着处理void,创建VoidElementHandler,设置setOwner和setParent。
image.png

调用父类com.sun.beans.decoder.ObjectElementHandler#addAttribute设置index属性
image.png
image.png

继续解析string标签,不再赘述。

解析完所有的开始标签之后,开始解析闭合标签,最开始就是,进入到endElement()
image.png

StringElementHandler没有endElement(),调用父类ElementHandler的endElement()
image.png

调用本类的getValueObject()
image.png
设置value为calc。

接着闭合void
image.png

闭合array
image.png

然后开始解析<void method="start"/>
image.png

通过父类的addAttribute将this.method赋值为start
image.png

随后闭合void标签
image.png

调用endElementVoidElementHandler类没有,所以调用父类ObjectElementHandler.endElement
image.png

调用NewElementHandler类无参getValueObject
image.png

然后调用VoidElementHandler类有参getValueObject,但是VoidElementHandler没有这个方法,所以调用VoidElementHandler父类ObjectElementHandler的有参getValueObject

protected final ValueObject getValueObject(Class<?> var1, Object[] var2) throws Exception {
        if (this.field != null) {
            return ValueObjectImpl.create(FieldElementHandler.getFieldValue(this.getContextBean(), this.field));
        } else if (this.idref != null) {
            return ValueObjectImpl.create(this.getVariable(this.idref));
        } else {
            Object var3 = this.getContextBean();
            String var4;
            if (this.index != null) {
                var4 = var2.length == 2 ? "set" : "get";
            } else if (this.property != null) {
                var4 = var2.length == 1 ? "set" : "get";
                if (0 < this.property.length()) {
                    var4 = var4 + this.property.substring(0, 1).toUpperCase(Locale.ENGLISH) + this.property.substring(1);
                }
            } else {
                var4 = this.method != null && 0 < this.method.length() ? this.method : "new";
            }

            Expression var5 = new Expression(var3, var4, var2);
            return ValueObjectImpl.create(var5.getValue());
        }
    }

跟进Object var3 = this.getContextBean(),因为本类没有getContextBean(),所以调用父类NewElementHandler的getContextBean()
image.png

继续调用NewElementHandler父类ElementHandler的getContextBean()
image.png

会调用this.parent.getValueObject()也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法
image.png
然后将值赋值给value返回

最终var3的值为java.lang.ProcessBuilder
image.png

var4赋值为start
image.png

通过Expression的getValue()方法反射调用start,弹出计算器。

Expression和Statement

两者都是Java对反射的封装,举个例子

package com.xml.java.beans;

public class User {
    private int id;
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String sayHello(String name) {
        return String.format("你好 %s!", name);
    }
}
package com.xml.java;

import com.xml.java.beans.User;

import java.beans.Expression;
import java.beans.Statement;

public class TestMain {
    public static void main(String[] args) {
        testStatement();
        testExpression();
    }

    public static void testStatement() {
        try {
            User user = new User();
            Statement statement = new Statement(user, "setName", new Object[]{"张三"});
            statement.execute();
            System.out.println(user.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void testExpression() {
        try {
            User user = new User();
            Expression expression = new Expression(user, "sayHello", new Object[]{"小明"});
            expression.execute();
            System.out.println(expression.getValue());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

张三
你好 小明!

Expression是可以获得返回值的,方法是getValue()。Statement不能获得返回值。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がスッキリわかるシリーズでJavaを学んでみた

はじめに

プログラミング初心者のスッキわかるシリーズのレビューです。
これからJavaの勉強をする方の参考になれば幸いです。

読んだ本

スッキリわかるJava入門 第3版
スッキリわかるJava実践編 第2版
スッキリわかるSQL 第2版
スッキリわかるサーブレット&JSP入門 第2版

スッキリわかるJava入門 

おすすめ度 ◎

良かった点

新入社員の男の子が先輩社員と一緒にRPGゲームを作っていくという流れで学習が進む。コンパイルや実行など、プログラミングのプの字も知らないような初心者向けに丁寧に書かれており、とても分かりやすかった。
ページ数は700ページを超えており、中々のボリュームだが、その分細かく、丁寧に解説してくれたのでスラスラと読むことができた。
理解が難しいと言われているオブジェクト指向を特に丁寧に解説しており、イメージを持ちながら学習することができた。
docojavaという開発環境を整えなくてもJavaを使える機能もあり、本を買っていきなりプログラミング学習を開始できるのも、この本の強みだと思う。
そもそもは、通販サイトやほとんどのまとめサイトでこの本はおすすめしていたことから、購入を決めたのだが、最初にこの本を買ってよかったと思っている。

悪かった点

ない。
が、強いて言うなら本が分厚いので持ち運びには適さない。
あと、ページ数が多いので、薄い本を何冊もやって達成感を得たい派には合わないのかな、と。

スッキリわかるJava実践編

おすすめ度 △

良かった点

スッキリわかるjava入門編の続編。
jarファイルの利用やジェネリクス、ファイル・データベースとのやり取りなど、入門編では書ききれなかった、(おそらく)実際の現場で多用するであろう技術を多く取り扱っていた。一つ一つが入門編よりも難しくなっており、一つずつ理解しながら丁寧にやっていたら、読むのに結構時間がかかった。が、以前よりもjavaでできることが増え、有意義な時間だったと思う。

悪かった点

より実践的・応用的な内容を学べると思って購入したが、実際は
「javaではこれが大事だよ!」
「これも大事!」
「あと、これも!」
みたいな感じで、各章で関連性が薄い知識を小出し小出しにしたような。
java初心者の重要な技術まとめ、の本。
もちろん全部大事なのは分かってる。
が、そうなるとわざわざ”この本”を買う理由はないのかな、と。
あと、載っているサンプルコードがエラーを起こすことが数回あり、その都度ネットで原因を調べる必要があった。(そのおかげで、自分で調べる力がついた。)
本の内容自体はとても分かりやすいが、超おすすめ!ではないかな。

スッキリわかるSQL 

おすすめ度 ○

良かった点

今回読んだ4冊の中で、圧倒的に分かりやすかった。
実践的な技術はともかく、SQLの基礎自体がそこまで難しくないので、内容も比較的わかりやすくなっているんだと思う。(初心者の推測です)
また、”この本”では、javaと絡めてSQLを利用することは一切なかった。
他の3冊と”この本”は、関連性が薄いことから、SQLの基礎を勉強するなら別の書籍でもいいのかな、と思った。SQLは必要な技術なので何らかの形で勉強した方がいいとは思う。

悪かった点

私は最初からPostgreSQLをインストールしていたので、一切使わなかったが、レビューによると、docoQLが使いにくいらしい。
といっても、上記2冊をやってからこの本をやれば、SQLのインストール自体はそこまで難しくないと思う。(本を進めると色々とインストールする機会がある)
あと、巻末の練習ドリル。全部やったらSQLのいい復習になると思う。
が、私は実際にデータベースを作って、データを入れてやりたい派なので、いちいちデータを打ち込むのがめんどくさくて、途中で投げました。

スッキリわかるサーブレット&JSP入門 

おすすめ度 ◎

良かった点

実際にwebアプリケーションを作るところまでいきます。
上記3冊の中で一番達成感がありました。
すべての章に関連性があり、これまでの知識を結集してアプリケーションを作るので、”開発している感”が持てた。本のサンプルといえど、一から何かを作ることは大事だと思った。
Pleiades(プレアデス)というwebアプリケーション開発に必要なソフトのインストールする必要があるが、インストール方法やその操作方法など、この本の専用サイトで丁寧に解説しているので、心配ないと思う。

悪かった点

ない。
ただ、サーブレット、jsp等のwebアプリケーションの知識・技術は奥がとてつもなく深くて、この本ではさわりの部分だけをやってるんだな、となんとなく感じた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JAVA関連】【短期】Qiitaの今読んでおくべき記事100選【毎日自動更新】

ページ容量を増やさないために、不具合報告やコメントは、説明記事 に記載いただけると助かります。

順位 記事名
________________________________________
ユーザ 投稿日付
更新日付
LGTM1
1 SwaggerでRESTful APIの管理を楽にする disc99 16/10/10
18/02/02
1244
45
2 30分で覚えるKotlin文法 k5n 16/02/10
18/03/30
838
50
3 Kotlin スコープ関数 用途まとめ ngsw_taro 15/09/29
18/05/16
746
50
4 スマホアプリ開発者のための2019年動向まとめ keidroid 20/01/17
20/01/24
472
472
5 JavaプログラマのためのKotlin入門 koher 17/05/18
17/12/22
1351
24
6 モダンなJava開発ガイド (2018年版) yoichiwo7 18/08/24
18/09/01
1112
42
7 忙しい人のためのIntelliJ IDEAショートカット集(´-`) yoppe 15/03/24
19/07/12
1739
0
8 Java8のラムダ式を理解する sano1202 17/05/22
19/07/29
433
36
9 【超初心者向け】Maven超入門 tarosa0001 17/07/01
18/10/31
454
28
10 Java 14新機能まとめ nowokay 20/02/13
20/04/05
417
417
11 Kotlin のコレクション使い方メモ opengl-8080 16/06/04
19/02/14
424
44
12 SpringBootに入門する為の助走本(随時更新) sugaryo 18/05/18
20/01/30
311
35
13 javaプログラマー向け学習のための本(新人から5年めくらいまで)を考えてみた bonybeat 16/06/11
19/08/09
1060
38
14 Java初心者時代にコードレビューで指摘された悪しき習慣 gengogo5 18/04/23
18/04/30
1034
14
15 KotlinとJavaができる人向けDart速習 kikuchy 18/09/18
20/01/08
297
40
16 Android Architecture Components 初級 ( MVVM + LiveData + Coroutines 編 ) Tsutou 18/05/30
19/11/10
342
29
17 MacのBrewで複数バージョンのJavaを利用する + jEnv seijikohara 17/10/03
20/03/21
329
25
18 フロントエンド、サーバサイド、インフラの具体例 shuntaro_tamura 14/10/13
16/03/16
1138
0
19 Thymeleafチートシート NagaokaKenichi 16/04/17
18/04/23
437
26
20 Spring Boot で Thymeleaf 使い方メモ opengl-8080 17/08/14
17/08/14
309
30
21 TOMCAT殺害事件 enoshiman 20/01/08
20/01/15
393
67
22 Spring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解する kazuki43zoo 16/05/13
19/07/22
526
16
23 2019年に注目すべきWebテクノロジー6選 rana_kualu 19/02/04
19/02/05
898
6
24 これだけは覚えたい、ユニットテストを書くための4つのデザイン koduki 17/01/15
18/02/20
820
14
25 Spring Framework / Spring Bootに入門する人はまずこの資料を読もう! #jsug suke_masa 19/08/29
20/02/07
441
39
26 [初心者]オブジェクト指向でなぜつくるのか IZUMIRU 19/06/02
19/06/02
480
13
27 OutOfMemoryError の調べ方 opengl-8080 16/03/13
18/04/05
816
16
28 Gradle の compile, api, implementation とかについて opengl-8080 18/05/25
18/05/25
239
24
29 `get()`を使うな ~ 敗北者の Optional BlueRayi 20/03/30
20/04/07
199
199
30 JavaプログラマがKotlinでつまづきがちなところ koher 17/05/22
17/12/29
630
12
31 令和時代に「Spring入門」「Spring徹底入門」を読むとき気をつけるべきN個のこと suke_masa 19/12/31
20/02/12
286
36
32 Seleniumで要素を選択する方法まとめ VA_nakatsu 18/02/08
19/01/15
177
22
33 あっと驚かせるJavaプログラミング(をやめよう) tatesuke 17/06/13
20/03/03
975
5
34 最初に押さえておきたいKotlin言語仕様 ssuzaki 16/04/06
20/01/24
231
26
35 JavaによるWebアプリケーションの仕組みをざっくり説明 mkdkkn 18/10/11
18/10/11
169
31
36 JDK、Oracle JDK、OpenJDK、Java SEってなに? nowokay 18/06/25
19/06/20
396
14
37 デザインパターン「Factory Method」 shoheiyokoyama 16/02/20
17/03/27
261
23
38 認証におけるJWTの利用について shnmorimoto 17/12/14
17/12/15
226
18
39 Kotlin の Coroutine を概観する kawmra 17/11/16
19/03/01
201
17
40 GoFのデザインパターンまとめ i-tanaka730 19/03/28
19/03/28
97
56
41 JDKの長期商用サポート(LTS)の提供ベンダー比較(無償利用についても言及あり) u-tanick 18/09/26
19/10/06
213
16
42 アジャイル開発を支えるためのCI/CD newta 16/12/02
16/12/02
240
18
43 Android Kotlin codelab courseで、今から最新のAndroidアプリ開発を始めよう takahirom 19/09/29
19/11/27
334
24
44 Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ disc99 14/11/09
19/03/15
2220
0
45 [Android] ConstraintLayout レイアウト逆引きまとめ tktktks10 18/08/30
19/09/18
126
28
46 新人プログラマに知ってもらいたいRabbitMQ初心者の入門の入門 gambaray 16/02/25
16/02/26
410
15
47 正規表現の基本 sea_ship 14/11/12
17/12/21
722
0
48 Javaのサポートについてのまとめ2018 nowokay 18/05/07
19/12/08
522
5
49 新人研修でドヤ顔で披露したらウケたEclipseのショートカット集 arai-wa 14/10/07
19/03/26
1854
0
50 削除済(ID:8595ca60a5c8d31bbe37) k-kagurazaka@github 17/12/02
18/03/29
346
6
51 Kotlinを勉強する際に役立つリソース集 r-kaga 18/08/17
19/06/30
174
15
52 Javaのパターンメモ nowokay 18/12/26
19/04/22
105
9
53 意外と知らないIntelliJ IDEAのGit管理機能いろいろ(´-`) yoppe 15/11/17
15/11/19
419
14
54 Spring MVC コントローラの引数 MizoguchiKenji 17/07/18
18/03/22
167
26
55 図で理解する Kotlin Coroutine kawmra 19/01/28
19/02/03
292
14
56 JavaのGCの仕組みを整理する e_tyubo 19/03/31
19/03/31
331
22
57 BCryptのすすめ ponkotuy 16/12/12
18/08/27
202
17
58 猿でも作れるサーバサイドKotlin入門【Spring Boot, Doma2】Part1 shierote 18/12/08
19/09/10
157
19
59 【Spring Data JPA】自動実装されるメソッドの命名ルール shindo_ryo 16/07/11
20/01/29
205
15
60 段階的に理解する Java 例外処理 ts7i 18/12/21
18/12/26
170
15
61 IntelliJ IDEA 入門 opengl-8080 16/04/06
18/04/02
390
16
62 Java8からJava11への変更点 nowokay 18/12/14
20/02/02
174
24
63 Spring での責務についてまず見てほしい一枚の絵 yo1000 16/04/09
18/08/07
239
15
64 「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した  mizunowanko 16/08/13
16/08/14
1364
15
65 Androidで個人的によく使うlayout系設定チートシート k_keisuke 17/08/04
20/03/03
195
21
66 Java Stream APIをいまさら入門 takumi-n 16/03/31
17/05/27
198
14
67 Awesome Java : 素晴しい Java フレームワーク・ライブラリ・ソフトウェアの数々 hatai 17/02/07
19/11/01
503
13
68 【初心者向け】10分で絶対にわかる!JavaBeansとは s_hino 17/03/29
17/04/04
140
17
69 Javadoc ドキュメンテーションコメントの書き方 maku77 14/10/04
19/07/16
772
0
70 RecyclerViewの基本 naoi 17/04/28
18/08/17
161
16
71 軽い気持ちでLinkedListを使ったら休出する羽目になった話 neko_machi 18/02/18
18/02/28
767
10
72 経験ゼロでもできるプログラミング現場の単体テスト disc99 14/02/19
16/11/03
727
0
73 Spring Bootの外部設定値の扱い方を理解する kazuki43zoo 16/09/11
17/12/22
264
12
74 javaを使ってWebアプリを作るまでに結局なにが必要なのか。仕組みと学習に必要なものをザックリ説明 shimatter 18/11/15
18/12/10
90
25
75 エニグマを実装してみた opengl-8080 19/08/15
19/08/18
354
3
76 Logback 使い方メモ opengl-8080 15/10/26
15/12/28
350
12
77 最近のAndroid開発でよく使われているっぽいライブラリまとめ(2019/11) gericass 19/05/26
19/11/19
280
11
78 Java ジェネリクスのポイント pebblip 15/07/20
15/07/21
391
9
79 Gradle を完全に理解した人が、何も分からなくなるための第一歩 opengl-8080 19/11/23
19/11/23
227
10
80 Spring BootのAutoConfigureの仕組みを理解する kazuki43zoo 16/09/06
16/09/06
272
10
81 Spring Security 使い方メモ 認証・認可 opengl-8080 17/04/27
18/09/06
193
10
82 実例によるkotlinx.coroutinesガイド(日本語訳) pljp 17/06/12
19/09/09
214
2
83 [Android] 10分で作る、Navigationによる画面遷移 tktktks10 18/09/15
19/10/29
119
14
84 脆弱性診断ツール OWASP ZAP vs 脆弱性だらけのWebアプリケーションEasyBuggy tamura__246 17/09/11
17/09/12
224
10
85 ちょっといいJavaコードを書こう nishemon 15/11/22
18/12/22
843
10
86 Spring MVC(+Spring Boot)上での静的リソースへのアクセスを理解する kazuki43zoo 16/05/05
17/02/12
265
12
87 Kotlin/Native を Android/iOS アプリ開発に導入しよう irgaly 18/10/12
18/10/17
407
13
88 「Java8からJava11」で何が起きたのか、どう環境構築すればいいのか to-lz1 20/01/10
20/03/13
70
65
89 メモリリーク、デッドロック、リダイレクトループ、JVMクラッシュ...バグだらけのWebアプリケーションを使ってバグを理解する tamura__246 17/02/14
18/01/22
1094
6
90 ジャバの異常な愛情 またはSpringはいかにしてモダンであることを止めて時代遅れになったのか psycho 19/10/03
20/03/13
308
19
91 mybatis-spring-boot-starterの使い方 kazuki43zoo 16/04/02
19/07/16
188
13
92 KubernetesネイティブなJavaフレームワーク Quarkus について調査してみた件 mamomamo 19/03/19
19/03/20
126
16
93 Spring Boot 使い方メモ opengl-8080 15/05/02
17/08/12
821
0
94 はじめてのKotlin。Javaと比較してみた hituziando 16/04/02
19/10/24
137
13
95 【Android Studio】ショートカット大好き人間がおくるショートカット集【IntelliJ IDEA】 arai-wa 17/03/27
17/08/19
166
16
96 図で理解するJavaのクラスとインスタンス hysdsk 17/07/06
17/08/09
144
5
97 翻訳: Kotlinベストプラクティス『Idiomatic Kotlin. Best Practices』 takahirom 17/07/02
19/11/20
367
10
98 Java 8 "Optional" ~ これからのnullとの付き合い方 ~ shindooo 14/09/13
18/05/04
598
0
99 【Java】formとentityとdtoの違いって?【Bean】 mtanabe 17/12/01
17/12/01
105
16
100 Spring Security with Spring Boot 2.0で簡単なRest APIを実装する rubytomato@github 18/04/10
18/05/10
119
12

  1. 1行目が総数。2行目が直近3ヵ月。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JAVA関連】【長期】Qiitaの今読んでおくべき記事100選【毎日自動更新】

ページ容量を増やさないために、不具合報告やコメントは、説明記事 に記載いただけると助かります。

順位 記事名
________________________________________
ユーザ 投稿日付
更新日付
LGTM1
1 忙しい人のためのIntelliJ IDEAショートカット集(´-`) yoppe 15/03/24
19/07/12
1739
191
2 SwaggerでRESTful APIの管理を楽にする disc99 16/10/10
18/02/02
1244
209
3 30分で覚えるKotlin文法 k5n 16/02/10
18/03/30
838
253
4 Kotlin スコープ関数 用途まとめ ngsw_taro 15/09/29
18/05/16
746
200
5 JavaプログラマのためのKotlin入門 koher 17/05/18
17/12/22
1351
174
6 フロントエンド、サーバサイド、インフラの具体例 shuntaro_tamura 14/10/13
16/03/16
1138
110
7 Java8のラムダ式を理解する sano1202 17/05/22
19/07/29
433
196
8 モダンなJava開発ガイド (2018年版) yoichiwo7 18/08/24
18/09/01
1112
148
9 Kotlin のコレクション使い方メモ opengl-8080 16/06/04
19/02/14
424
156
10 【超初心者向け】Maven超入門 tarosa0001 17/07/01
18/10/31
454
170
11 javaプログラマー向け学習のための本(新人から5年めくらいまで)を考えてみた bonybeat 16/06/11
19/08/09
1060
154
12 Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ disc99 14/11/09
19/03/15
2220
82
13 Thymeleafチートシート NagaokaKenichi 16/04/17
18/04/23
437
128
14 正規表現の基本 sea_ship 14/11/12
17/12/21
722
76
15 Spring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解する kazuki43zoo 16/05/13
19/07/22
526
137
16 新人研修でドヤ顔で披露したらウケたEclipseのショートカット集 arai-wa 14/10/07
19/03/26
1854
92
17 SpringBootに入門する為の助走本(随時更新) sugaryo 18/05/18
20/01/30
311
190
18 Java初心者時代にコードレビューで指摘された悪しき習慣 gengogo5 18/04/23
18/04/30
1034
54
19 これだけは覚えたい、ユニットテストを書くための4つのデザイン koduki 17/01/15
18/02/20
820
132
20 MacのBrewで複数バージョンのJavaを利用する + jEnv seijikohara 17/10/03
20/03/21
329
158
21 OutOfMemoryError の調べ方 opengl-8080 16/03/13
18/04/05
816
107
22 Android Architecture Components 初級 ( MVVM + LiveData + Coroutines 編 ) Tsutou 18/05/30
19/11/10
342
166
23 Spring Boot で Thymeleaf 使い方メモ opengl-8080 17/08/14
17/08/14
309
130
24 JavaプログラマがKotlinでつまづきがちなところ koher 17/05/22
17/12/29
630
86
25 あっと驚かせるJavaプログラミング(をやめよう) tatesuke 17/06/13
20/03/03
975
52
26 2019年に注目すべきWebテクノロジー6選 rana_kualu 19/02/04
19/02/05
898
120
27 KotlinとJavaができる人向けDart速習 kikuchy 18/09/18
20/01/08
297
169
28 Javadoc ドキュメンテーションコメントの書き方 maku77 14/10/04
19/07/16
772
71
29 経験ゼロでもできるプログラミング現場の単体テスト disc99 14/02/19
16/11/03
727
76
30 新人プログラマに知ってもらいたいRabbitMQ初心者の入門の入門 gambaray 16/02/25
16/02/26
410
86
31 デザインパターン「Factory Method」 shoheiyokoyama 16/02/20
17/03/27
261
93
32 最初に押さえておきたいKotlin言語仕様 ssuzaki 16/04/06
20/01/24
231
106
33 意外と知らないIntelliJ IDEAのGit管理機能いろいろ(´-`) yoppe 15/11/17
15/11/19
419
69
34 アジャイル開発を支えるためのCI/CD newta 16/12/02
16/12/02
240
86
35 スマホアプリ開発者のための2019年動向まとめ keidroid 20/01/17
20/01/24
472
472
36 削除済(ID:8595ca60a5c8d31bbe37) k-kagurazaka@github 17/12/02
18/03/29
346
78
37 Gradle の compile, api, implementation とかについて opengl-8080 18/05/25
18/05/25
239
113
38 Javaのサポートについてのまとめ2018 nowokay 18/05/07
19/12/08
522
49
39 Spring Boot 使い方メモ opengl-8080 15/05/02
17/08/12
821
58
40 JDK、Oracle JDK、OpenJDK、Java SEってなに? nowokay 18/06/25
19/06/20
396
108
41 Java 8 "Optional" ~ これからのnullとの付き合い方 ~ shindooo 14/09/13
18/05/04
598
56
42 [初心者]オブジェクト指向でなぜつくるのか IZUMIRU 19/06/02
19/06/02
480
480
43 IntelliJ IDEA 入門 opengl-8080 16/04/06
18/04/02
390
61
44 認証におけるJWTの利用について shnmorimoto 17/12/14
17/12/15
226
100
45 Kotlin の Coroutine を概観する kawmra 17/11/16
19/03/01
201
111
46 Java ジェネリクスのポイント pebblip 15/07/20
15/07/21
391
63
47 Seleniumで要素を選択する方法まとめ VA_nakatsu 18/02/08
19/01/15
177
125
48 Spring MVCのコントローラでの戻り値いろいろ tag1216 14/12/17
14/12/17
510
62
49 【Spring Data JPA】自動実装されるメソッドの命名ルール shindo_ryo 16/07/11
20/01/29
205
79
50 Java 14新機能まとめ nowokay 20/02/13
20/04/05
417
417
51 Gradle使い方メモ opengl-8080 13/12/07
19/02/26
681
53
52 BCryptのすすめ ponkotuy 16/12/12
18/08/27
202
66
53 Spring での責務についてまず見てほしい一枚の絵 yo1000 16/04/09
18/08/07
239
76
54 「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した  mizunowanko 16/08/13
16/08/14
1364
71
55 Spring Framework / Spring Bootに入門する人はまずこの資料を読もう! #jsug suke_masa 19/08/29
20/02/07
441
441
56 もう参照渡しとは言わせない mdstoy 14/04/24
19/05/03
419
32
57 Spring Bootの外部設定値の扱い方を理解する kazuki43zoo 16/09/11
17/12/22
264
66
58 Java Stream APIをいまさら入門 takumi-n 16/03/31
17/05/27
198
75
59 Java8の日時APIはとりあえずこれだけ覚えとけ tag1216 15/05/17
17/07/19
496
43
60 Logback 使い方メモ opengl-8080 15/10/26
15/12/28
350
66
61 JDKの長期商用サポート(LTS)の提供ベンダー比較(無償利用についても言及あり) u-tanick 18/09/26
19/10/06
213
123
62 メモリリーク、デッドロック、リダイレクトループ、JVMクラッシュ...バグだらけのWebアプリケーションを使ってバグを理解する tamura__246 17/02/14
18/01/22
1094
24
63 Spring BootのAutoConfigureの仕組みを理解する kazuki43zoo 16/09/06
16/09/06
272
65
64 JavaによるWebアプリケーションの仕組みをざっくり説明 mkdkkn 18/10/11
18/10/11
169
122
65 Awesome Java : 素晴しい Java フレームワーク・ライブラリ・ソフトウェアの数々 hatai 17/02/07
19/11/01
503
72
66 Spring MVC コントローラの引数 MizoguchiKenji 17/07/18
18/03/22
167
78
67 実例によるkotlinx.coroutinesガイド(日本語訳) pljp 17/06/12
19/09/09
214
61
68 軽い気持ちでLinkedListを使ったら休出する羽目になった話 neko_machi 18/02/18
18/02/28
767
49
69 削除済(ID:98ffbeaee42d30cca4dc) lrf141 15/07/12
16/10/23
310
52
70 ちょっといいJavaコードを書こう nishemon 15/11/22
18/12/22
843
68
71 Kotlinを勉強する際に役立つリソース集 r-kaga 18/08/17
19/06/30
174
117
72 Scala入門時に役立つ情報まとめ nesheep5 15/11/09
18/03/22
507
35
73 Spring MVC(+Spring Boot)上での静的リソースへのアクセスを理解する kazuki43zoo 16/05/05
17/02/12
265
58
74 RecyclerViewの基本 naoi 17/04/28
18/08/17
161
72
75 Androidで個人的によく使うlayout系設定チートシート k_keisuke 17/08/04
20/03/03
195
71
76 Groovyを知らない人のためのbuild.gradle読み書き入門 opengl-8080 14/12/14
19/01/13
506
57
77 【初心者向け】10分で絶対にわかる!JavaBeansとは s_hino 17/03/29
17/04/04
140
82
78 Spring Security 使い方メモ 認証・認可 opengl-8080 17/04/27
18/09/06
193
62
79 図で理解する Kotlin Coroutine kawmra 19/01/28
19/02/03
292
119
80 脆弱性診断ツール OWASP ZAP vs 脆弱性だらけのWebアプリケーションEasyBuggy tamura__246 17/09/11
17/09/12
224
52
81 [Android] ConstraintLayout レイアウト逆引きまとめ tktktks10 18/08/30
19/09/18
126
105
82 Kotlin文法 - 関数とラムダ k5n 16/01/28
16/01/29
186
47
83 TOMCAT殺害事件 enoshiman 20/01/08
20/01/15
393
393
84 mybatis-spring-boot-starterの使い方 kazuki43zoo 16/04/02
19/07/16
188
60
85 幸せな非同期処理ライフを満喫するための基礎から応用まで KeithYokoma 15/03/24
16/11/16
620
35
86 はじめてのKotlin。Javaと比較してみた hituziando 16/04/02
19/10/24
137
57
87 業務システムにおけるロールベースアクセス制御 kawasima 15/12/02
15/12/02
287
49
88 段階的に理解する Java 例外処理 ts7i 18/12/21
18/12/26
170
109
89 JavaのGCの仕組みを整理する e_tyubo 19/03/31
19/03/31
331
91
90 プログラミング言語を作る。1時間で。 shuetsu@github 16/09/27
16/10/03
645
46
91 猿でも作れるサーバサイドKotlin入門【Spring Boot, Doma2】Part1 shierote 18/12/08
19/09/10
157
107
92 Jackson使い方メモ opengl-8080 14/08/09
14/08/09
551
32
93 オブジェクト指向エクササイズをやってみる opengl-8080 16/03/19
16/05/28
371
35
94 Android Kotlin codelab courseで、今から最新のAndroidアプリ開発を始めよう takahirom 19/09/29
19/11/27
334
334
95 Scalaの記号みたいな奴らなんなの harry0000 16/05/18
19/11/12
245
50
96 図で理解するJavaのクラスとインスタンス hysdsk 17/07/06
17/08/09
144
69
97 MyBatis 使い方メモ opengl-8080 16/01/03
16/01/03
233
45
98 Kotlin/Native を Android/iOS アプリ開発に導入しよう irgaly 18/10/12
18/10/17
407
45
99 Mockito 初めの一歩 mstssk 13/06/13
14/04/01
444
29
100 翻訳: Kotlinベストプラクティス『Idiomatic Kotlin. Best Practices』 takahirom 17/07/02
19/11/20
367
47

  1. 1行目が総数。2行目が直近1年。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mavenの基本勉強メモ

ずっと 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.xml
<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フォルダの中身
hello-world/
|-pom.xml
`-src/
  |-main/java/
  | `-com/example/
  |    `-App.java
  `-test/java/
    `-com/example/
      `-AppTest.java
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.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>
App.java
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 のコンパイル結果が出力されている
  • 以下のコマンドで実行する
hello-worldを実行する
> 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 の内容は次のようになる。

最小構成の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 は、そのプロジェクトのバージョンを指定する
      • groupIdartifactId によってプロジェクトが一意に特定され、さらに 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 の中では、同じ値を複数の箇所で重複して記述してしまう問題を回避するため、変数を参照できるようになっている。

プロジェクトモデル変数

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 の中で宣言されている

式の構文

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.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の参照

pom.xml
<?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() は、そのプラグインの groupIdartifactId をコロン (:) でつなげた値を返す
    • project.build で参照できる Build クラスは、この PluginContainer を継承している
  • このように、一部のクラスには ListMap 形式に変換したプロパティを提供しているものがある

各クラスのプロパティ

Model から参照できる各クラスやプロパティの全体像をクラス図でまとめた。

maven.jpg

  • 赤線が継承
  • 青線が単一の参照
  • 緑線が List での参照

特殊変数

  • プロジェクトモデルには含まれていないが、特別に定義されていて参照が可能な変数がいくつか存在する
pom.xml
<?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 に従う
pom.xml
<?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

プロパティ

pom.xml
<?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.pom.xml
<?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>
実行結果(Windows上で動かした場合)
> set Hoge=Hello

> mvn antrun:run
...
     [echo] hoge = ${env.hoge}
     [echo] Hoge = ${env.Hoge}
     [echo] HOGE = Hello
...
実行結果(Linux上で動かした場合)
$ 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 上での話で、 Linux 上で動かす場合は大文字・小文字を区別したそのままの名前で指定する
  • まぁ、環境変数は全部大文字で宣言するのが一般的だと思うので、 pom.xml も全部大文字で書いておけば事故ることはないと思う

システムプロパティ

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 では、そのプロジェクトが使用するアーティファクトを依存関係として次のように定義できる
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 はその依存アーティファクトも自動的にダウンロードしてくれる
    • つまり、芋づる式にすべての依存関係を解決してくれる仕組みになっている
  • Maven は、この仕組みのおかげで依存ライブラリの管理が非常に楽になっている
    • Maven の前身である Apache Ant は依存関係を手動で管理しなければならかったので、非常に辛かった
    • この仕組みは、 Maven の後発である Gradle でも利用されている

ローカルリポジトリ

  • リモートリポジトリからダウンロードしてきたアーティファクトやメタ情報(pom.xml とか)は、 Maven を実行したマシンのローカルにキャッシュされる
  • このキャッシュ先のことを、ローカルリポジトリと呼ぶ
  • 毎回ビルドのたびにリモートリポジトリにアクセスしていると、時間がかかるしネットワークが使用できない環境ではビルドもできなくなってしまう
  • したがって、 Maven はまず先にローカルリポジトリを検索するようになっている
    • ローカルリポジトリに目的のアーティファクトが存在しない場合に、リモートリポジトリに検索に行くようになっている
  • リモートリポジトリでアーティファクトが見つかった場合は、それをダウンロードし、ローカルリポジトリに保存する
    • これにより、2回目以降はローカルリポジトリを参照すれば良くなるので、ネットワークアクセスなしでもプロジェクトをビルドできる
  • ローカルリポジトリの場所は、デフォルトでは %USERPROFILE%\.m2\repository になる
    • Linux 系の OS なら $HOME/.m2/repository
  • ローカルリポジトリにキャッシュされたアーティファクトは、何もしなければ残り続ける
    • ディスク容量が足りなくなったなどの理由がない限りは、特に消す必要もない

プライベートリポジトリ

  • Nexus Repository OSS という OSS のアプリケーションを使用すると、独自のリモートリポジトリを構築できる
    • Nexus はセントラルリポジトリを管理している Sonatype 社によって開発されている
    • 有償版と無償の OSS 版がある
  • 例えば、社内だけで共有したいアーティファクトがある場合に、イントラネット内で Nexus サーバーを構築すれば簡単にプライベートリポジトリとして利用できる
  • 使用するリモートリポジトリは、 pom.xml で次のように指定できる
    • ホスト名やポート番号は実際の環境に合わせて要調整
リモートリポジトリを指定した場合の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.jpg

プラグイン(基本)

Maven は、すべての処理がプラグイン(Plugin)によって行われる。

例えば、 Java のソースコードをコンパイルするための処理は maven-compiler-plugin によって提供されている。

Maven プロジェクト自身が提供する基本的なプラグインは、Maven – Available Plugins で一覧を確認できる。

プラグインのゴール(Goal)

  • プラグインに定義された個々のタスク(処理)のことをゴール(Goal)と呼ぶ
  • 使用したいプラグインがどのようなゴールを持つかは、それぞれのプラグインのドキュメントを参照する

ゴールの実行方法

プラグインのゴールを実行する方法は、大きく次の2つがある。

  1. コマンドラインで直接指定して実行する
  2. フェーズに紐付けて実行する

2つ目のフェーズに紐付ける方法は置いておいて、先に1の直接指定する方法について確認する。

完全修飾名指定

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>
jdepsプラグインを実行する
> 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 指定の場合、次のような感じで完全修飾名が解決される
  • まず、 groupdId を以下のいずれかとする
    • org.apache.maven.plugin
    • org.codehaus.mojo
  • 次に、 groupId ごとにメタデータ (maven-metadata.xml) を調べる
  • maven-metadata.xml の中身を見ると、 artifactId ごとに prefix の対応が列挙されているのがわかる
org.apache.maven.pluginのmaven-metadata-xml
<?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-pluginartifactId になる
  • 次は、 artifactId ごとのメタデータ(maven-metadata.xml)を調べる
maven-jdeps-pluginの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>5version とする
  • 以上で決定した groupId, artifactId, version を、完全修飾名とする
    • groupId : org.apache.maven.plugin
    • artifactId : maven-jdeps-plugin
    • version : 3.1.2

プラグインを明示的に設定する

  • ↑の方法はメタデータを元にプラグインのバージョンが決まっている
    • おそらく新しいバージョンが出たら最新版を使うことになるが、動きを固定できないとビルドが不安定になる恐れがある
    • したがって、通常はプロジェクトごとに使用するプラグインのバージョンを固定する
  • また、↑の場合 prefix 指定ができるのは groupIdorg.apache.maven.pluginorg.codehaus.mojo のいずれかの場合に限られる6
    • これら以外の groupId のプラグインを prefix 指定で利用したい場合は、プラグインを明示的に設定する必要がある
pom.xml
<?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 に固定されることになる
asciidoctor-maven-pluginを実行している様子
> mvn asciidoctor:process-asciidoc
...
[INFO] BUILD SUCCESS
...

prefix 指定の解決(プラグインの指定がある場合)

  • ↑のように <plugin> でプラグインが明示されている場合、 prefix 指定から完全修飾名を解決する方法が少し変わる
  • まず Maven は、 pom.xml で明示的に設定されている各プラグインの plugin.xml を確認していく
    • 普通、 plugin.xml はプラグインの jar 内の META-INF/maven の下に格納されている
  • 例えば、 asciidoctor-mavne-pluginplugin.xml は次のようになっている
asciidoctor-maven-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 と等しいプラグインの groupIdartifactId が採用される
  • 最後に、 version は次のように決まる
    • pom.xml で指定されていれば、そのバージョンが採用される
    • 指定されていない場合は、 artifactId ごとのメタデータ(maven-metadata.xml)から決定される(プラグイン指定なしの場合と同じ方法)

artifactId と prefix の関係

  • ここまでの説明で、 artifactId と prefix には、本質的には関連がないことがわかる
    • maven-metadata.xml<prefix> か、 plugin.xml<goalPrefix> で prefix が決まっている
  • しかし、実際に存在するプラグインたちには、 artifactId と prefix に次のような関係がある
    • Maven プロジェクトが提供している公式プラグインの場合
      • artifactIdmaven-XXX-plugin の場合、 XXX が prefix となる
    • その他のプラグインの場合
      • artifactIdXXX-maven-plugin の場合、 XXX が prefix となる
  • これらは、そういう命名ルールが推奨されている結果であって、この名前じゃないとプラグインが作れないわけではない
    • 作ろうと思えば、この命名ルールとは全く異なる artifactId のプラグインを作ることもできる
    • ただし、 maven-XXX-plugin という命名は、それが Maven の公式プラグインであること表すためのものなので、公式以外がこの命名を使うと商標侵害になるので要注意
    • 一般的でない命名をしたところで利用者が混乱するだけでメリットがないので、プラグインを自作するなら特別な理由がない限りは XXX-maven-plugin という artifactId にしておくのが無難
  • ということで、 artifactId と prefix は本質的には関連しないが、実用上は関連しているものと思って問題ない(と思う)
    • なので、 artifactIdfoo-maven-plugin なら、 prefix は foo と思っていい(と思う)
    • 逆に、 prefix が foo なら、 artifactIdfoo-maven-plugin と思っていい(と思う)(Maven プロジェクトが提供しているプラグインでない場合)

プラグインの設定(パラメータ)

pom.xml
<?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 で確認することもできる
pom.xml
<?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> で詳細(ゴールごとに指定できるパラメータ)の出力もできる
pom.xml
<?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> で指定していたパラメータは、システムプロパティから指定することもできる
pom.xml
<?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 を指定している
  • このように、ゴールのパラメータの中にはシステムプロパティで値を渡すことができるものがある
    • すべてのパラメータがシステムプロパティで指定できるわけではない
  • あるパラメータがシステムプロパティで指定できるかどうかは、そのパラメータのドキュメントに User property が書かれているかどうかで確認できる
    • 例えば、 plugin パラメータのドキュメント では、 User Property: plugin と書かれているのが確認できる
    • これは、 plugin という名前のプロパティで値を指定できることを表している
    • パラメータ名とプロパティ名は常に一致しているわけではない
      • plugin はたまたま一致しているだけで、 antrun の skip のように一致していないものもある
  • ここでプロパティと言っているのは、システムプロパティに限らず pom.xml 上の <properties> でも指定できることを表している
pom.xml
<?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> で、 pluginjdeps を設定している
実行結果(システムプロパティでpluginを指定しない場合)
> 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
...
実行結果(システムプロパティでpluginを指定した場合)
> 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 プログラムをビルドするには、次のような処理が考えられる
    1. ソースコードをコンパイルする
    2. リソースファイルを収集する
    3. テストコードをコンパイルする
    4. テスト用のリソースファイルを収集する
    5. テストコードを実行する
    6. コンパイル結果とリソースファイルをまとめて、 jar などのアーカイブにパッケージングする
    7. アーカイブを所定の場所に格納する
  • Maven では、これら1つ1つの処理をフェーズ(Phase)と呼ぶ
  • そして、一連のフェーズのセットをライフサイクル(Lifecycle)と呼ぶ

組み込みのライフサイクル

  • Maven には、標準で次の3つのライフサイクルが用意されている
    • default
    • clean
    • site

default ライフサイクル

  • default ライフサイクルは、プロジェクトをビルドしてデプロイするまでのライフサイクルを定義している
  • default ライフサイクルは、次のフェーズで構成されている
    1. validate
    2. initialize
    3. generate-sources
    4. process-sources
    5. generate-resources
    6. process-resources
    7. compile
    8. process-classes
    9. generate-test-sources
    10. process-test-sources
    11. generate-test-resources
    12. process-test-resources
    13. test-compile
    14. process-test-classes
    15. test
    16. prepare-package
    17. package
    18. pre-integration-test
    19. integration-test
    20. post-integration-test
    21. verify
    22. install
    23. deploy

clean ライフサイクル

  • clean ライフサイクルは、プロジェクトの成果物を削除するライフサイクルを定義している
  • clean ライフサイクルは、次のフェーズで構成されている
    1. pre-clean
    2. clean
    3. post-clean

site ライフサイクル

  • site ライフサイクルは、プロジェクトの Web サイト生成のライフサイクルを定義している
  • site ライフサイクルは、次のフェーズで構成されている
    1. pre-site
    2. site
    3. post-site
    4. 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-pluginclean ゴールが実行されることになる

default ライフサイクルの場合

  • default ライフサイクルの場合、フェーズとゴールの紐付けは固定ではない
  • default ライフサイクルで実行されるゴールは、そのプロジェクトの packaging によって変化する

packaging

  • packaging とは、そのプロジェクトをどのようにパッケージ化するかを決める設定値で、次のいずれかの値が指定できる
    • pom
    • jar
    • ejb
    • maven-plugin
    • war
    • ear
    • rar
  • この packaging は、 pom.xml 上で次のように指定する
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project ...>
  ...
  <packaging>jar</packaging>
  ...
</project>
  • ここでは、 packaging として jar を指定している
  • なお、 <packaging> タグは pom.xml で省略することができる
    • 省略した場合、デフォルトは jar になる

packaging ごとのゴール

  • default ライフサイクルのフェーズに紐付けられているゴールは、 packaging によって次のように異なる
  • 一例として、 pomjar に紐付けられたゴールは次のようになっている

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

フェーズを実行する

pom.xml
<?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>
testフェーズを実行する
> 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 フェーズの前には validateprocess-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 コマンドに複数指定して実行できる
  • この場合の実行順序は、 cleancompiler:compile の順序になる
  • つまり、引数で指定した順番で実行されるようになっている

フェーズにゴールを紐付ける

pom.xml
<?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-pluginrun ゴールを紐付けている
  • <goals><goal> という構造からも分かる通り、1つのフェーズに対して同じプラグインの複数のゴールを紐付けることもできる
pom.xml
<?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-pluginjdkinternalstest-jdkinternalsverify フェーズに紐づけている
  • 1つのフェーズに複数のゴールが紐付けられている場合、それらのゴールは pom.xml 上で宣言されている順番で実行される7
    • つまり、↑の設定の場合は jdkinternalstest-jdkinternals の順番で実行される
    • <goal> タグの順序を変えれば、実行順序を変更できる

複数のフェーズに紐付ける

pom.xml
<?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 ごとに設定する

pom.xml
<?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

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>
        <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> を指定してゴールを実行することができるようになっている
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>
        <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-clifoo の、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> が実行される

デフォルトのフェーズ

pom.xml
<?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 ゴールが実行されていることがわかる
  • ゴールは、デフォルトで紐づくフェーズを定義できるようになっている
  • もし <execution> でフェーズが明示されていない場合は、このデフォルトで紐付いているフェーズで実行されるようになっている
  • あるゴールにフェーズが1つも紐付いていない場合、そのゴールはコマンドラインで直接指定しなければ起動できない

プラグインのバージョン指定

  • プラグインは、 <plugin> で明示的に設定しなくてもある程度使用できる
    • packaging の設定によって、いくつかの基本的なプラグイン(maven-compiler-plugin など)は自動的に適用される
    • プレフィックス指定された場合は、 groupId, artifactId, version が自動解決される
  • 設定がなくても動くのであれば、わざわざ <plugin> を書かなくても良いような気がする
  • しかし、 <plugin> の設定無しで実行する方法は、プラグインのバージョンが実行のたびに変わる可能性がある
  • 例えば、 packaging によって自動設定されるプラグインは、 Maven のバージョンによって変化する可能性がある
    • たとえば、 3.6.3 で設定されるプラグインのバージョンはここで確認できる
    • 開発者の使っている Maven のバージョンが揃っていないと、開発者ごとにビルド結果が変わるかもしれない
    • (そもそも、デフォルトで設定されるプラグインのバージョンは古い)
  • また、プレフィックス指定で解決されるプラグインのバージョンは、基本的にリポジトリの最新になる
    • これも、実行時の最新が変われば結果も変化する恐れがある
  • したがって、使用するプラグインのバージョンは、面倒だと思っても基本的に明示するのが良い
  • ただし、ここに記載している pom.xml の例は、以下の理由によりプラグインの設定を省略していることがある
    • 記述量が多くなると、それだけで読む気が失せる
    • 必要な部分(今説明しようとしている部分)に注目できない

プロジェクトの親子関係

  • プロジェクトには親子関係をもたせることができる
フォルダ構成
|-pom.xml
`-child/
  `-pom.xml
  • トップフォルダに親プロジェクトの pom.xml を置き、 child フォルダの下に子プロジェクトの pom.xml を置いている
/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 を指定しなければならない
/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>

  <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
実行結果(childフォルダ)
> mvn antrun:run
...
     [echo] artifactId = child
  • <plugins> の設定も引き継がれているため、親プロジェクトで設定された antrun プラグインを子プロジェクトでも使用できている

親プロジェクトの解決方法

  • <parent> で設定された親の pom.xml は、次の順序で検索される
    1. <relativePath> が指定されている場合は、その場所の pom.xml を参照
    2. <relativePath> が指定されていない場合は、1つ上の階層の pom.xml を参照
    3. それも無ければ、リポジトリ(ローカル or リモート)を検索して参照
  • ↑の例は親プロジェクトが子プロジェクトの1つ上だったため、特に場所の指定をしなくても親 POM が見つかっていた

親プロジェクトの場所を明示する

  • しかし、↓のようなフォルダ構成の場合は、 <relativePath> によるパスの指定が必要になる
フォルダ構成
|-parent/
| `-pom.xml
`-child/
  `-pom.xml
  • parentchild が横並びになっており、子プロジェクトの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> で親プロジェクトの場所を明示してあげる必要がある
/child/pom.xml
<?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がマージされる
  • マージがどのように行われるのか確認してみる
親の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>

  <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 プラグインで設定内容を表示できるようにしている
子の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>

  <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> は追加する形でマージされることになっている
    • しかし、実際にはこの ModelMerger のサブクラスである MavenModelMerger でマージが行われているっぽい
      • MavenModelMergermergeBuildBase_Resources() をオーバーライドしていて、 target (子POM)の <resources> が空の場合のみ親POMの内容をマージするようにしている
      • つまり、子POMに <resources> が存在すればマージは行われず、子POMの内容だけが採用される(結果として上書きの動作になる)
    • <resources> 以外は、この MavenModelMerger の実装を見てみるしかない?

マージ後の POM を確認する

  • POM には親子関係があり、複数の POM を親に持つような場合は末端の POM を見ただけでは最終的な状態がわからない
  • 特にビルドがうまく動作しないときは、 POM が期待通りにマージされているか確認したくなることがよくある
  • そういうときは、 maven-help-plugineffective-pom ゴールが利用できる
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> の下のタグはそういう識別ができないので、だいたい同じ位置のタグをそれっぽくマージする感じになる
      • (正確な仕様や実装を確認したわけではないので、そういう雰囲気というくらいの気持ちで)
  • どういうふうにマージされるのか、実際の例で見てみる
親POM
      ...
        <configuration>
          <persons>
            <person>
              <name>Taro</name>
              <age>18</age>
            </person>
            <person>
              <name>Hanako</name>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
子POM
      ...
        <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 でマージ結果を確認する。

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>
      ...
  • 言葉で説明するのは難しいが、なんかいい感じにマージされているのが分かると思う
    • おそらく、同じ位置に同名のタグがあれば、その中身を再帰的にマージしていっている感じだと思う

マージの仕方を制御する

  • デフォルトの動作でも、そこそこよしなにマージしてくれる気がする
  • しかし、それだと色々問題があるという場合は、デフォルトのマージの挙動を変更することもできる
親POM
      ...
        <configuration>
          <persons>
            <person>
              <name>Taro</name>
              <age>18</age>
            </person>
            <person>
              <name>Hanako</name>
              <sex>female</sex>
            </person>
          </persons>
        </configuration>
      ...
  • 親POMは変更なし
子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" という属性を追加している
effective-pom
      ...
        <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.* 属性を指定する必要がある

親プロジェクトで定義だけをまとめる

  • 親プロジェクトの設定は、常に子プロジェクトに継承される
  • すべての子プロジェクトで共通な設定の場合は便利だが、一部の子プロジェクトでだけ必要な設定の場合は、余計な継承が発生してしまう
  • そこで、定義だけを親プロジェクトにまとめて、実際の適用はその定義を使用したい子プロジェクトで明示的に行う方法が用意されている

プラグインの定義をまとめる

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

  <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> を記述できる
  • ただし、ここで記述したものはあくまで設定を定義しただけで、プロジェクトには適用されていない
子1のpom.xml
<?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> に記述したものが使用される
  • 子プロジェクトで個別の設定を追加することも可能
子2のpom.xml
<?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>
  • こちらは、特に何もプラグインを適用していない

実行結果(子1)
> 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で定義された設定で、プラグインが適用されている
実行結果(子2)
> mvn validate
...
[INFO] ---------------------------< example:child2 >---------------------------
[INFO] Building child2 1.0.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • プラグインは何も適用されていない

依存関係の定義をまとめる

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

  <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> 同様、ここでの定義は宣言のみで、プロジェクトへの適用は個別に行う
子1のpom.xml
<?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> を上書き指定することも可能
子2のpom.xml
<?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>
  • こちらは、特に依存関係を何も定義していない

実行結果(子1のeffective-pom)
> 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 が適用されている
実行結果(子2のeffective-pom)
> 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 フォルダの下に、集約対象となるプロジェクトが存在する
集約元の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>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 にしておく必要がある
集約対象のpom.xml
<?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
親の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の構成は、とくに変化無し
子の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>

  <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 ライフサイクルで実行されるゴールは以下になる
    1. resources:resources
    2. compiler:compile
    3. resources:testResources
    4. compiler:testCompile
    5. surefire:test
    6. jar:jar
    7. install:install
    8. deploy:deploy
  • 各ゴールが何をしているのか、1つずつ見ていく

リソースを収集する

  • 最初に実行されるのは、 maven-resources-pluginresources ゴール となっている
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>
  • 最小構成の 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 が設定されている
  • 出力先のフォルダは、 resources ゴールの outputDirectory パラメータで指定された場所となっている
    • このパラメータには、デフォルトで ${project.build.outputDirectory} が設定されている
    • そして、この値は Super POM によってデフォルトで ${project.build.directory}/classes が設定されている
    • さらに、 ${project.build.directory} には ${project.basedir}/target が設定されている
  • ということで、例えば次のように 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>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/resourcessrc/main/hoge の2つを用意している
resourcesゴールを実行
> 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 ソースをコンパイルする

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>
  • 最小構成の POM で検証する
フォルダ構成
|-pom.xml
`-src/main/java
  `-example/
    `-Hello.java
  • Hello.java だけを配置したシンプルな構成
compileゴールを実行
> 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 の指定を上げる必要がある
  • 動作を確定させるためにも、両方とも指定しておくのが無難
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>

  <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-pluginsource を指定するには、 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 など)となっている
  • ということで、エンコーディングも設定してみる
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>

  <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 上で参照しようとしても、値を確認することはできない
    • compileSourceRootsMavenProject クラスのフィールドとして宣言されている
    • このフィールドは、 DefaultProjectBuilder によって値が設定されている
    • この実装から、 project.build.sourceDirectorycompileSourceRoots に設定されていることが分かる
    • そして、 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 の下にテスト用のリソースファイルを配置する
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>

  <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> で指定できる

テストを実行する

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>

  <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 に固める

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>

  <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
packageフェーズを実行
> 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 を解凍すると、中身は次のようになっている
hello-1.0.0.jarの中身
|-hoge.txt
|-example/
| `-Hello.class
`-META-INF/
  |-MANIFEST.MF
  `-maven/example/hello/
    |-pom.properties
    `-pom.xml
  • hoge.txtHello.class は、それぞれ resources:resources, compiler:compile によって target/classes の下に出力されたものが梱包されている
    • どのフォルダの内容が jar に梱包されるかは、 jar ゴールの classesDirectory パラメータによって設定されている
      • このパラメータのデフォルトは、 ${project.build.outputDirectory} となっている(${project.basedir}/target/classes
  • META-INF の下に pom.xml が出力されているが、その内容はこのプロジェクトの pom.xml と同じものが入っている
  • MANIFEST.MFpom.properties は、それぞれ次のような内容になっている
MANIFEST.MF
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven 3.6.3
Built-By: xxxxx
Build-Jdk: 11.0.6
pom.properties
#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.MFMETA-INF 以下に追加される情報を設定できる

ローカルリポジトリにインストールする

  • install フェーズに紐付けられている maven-install-plugininstall ゴール は、ローカルリポジトリにプロジェクトの成果物をインストールする機能を提供する
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>
installを実行
> mvn install
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...
  • install:install ゴールは install フェーズに紐付いているので、 install フェーズを実行している(ややこしい)
  • 実行が完了したら、ローカルリポジトリの中を確認する
    • ローカルリポジトリの場所は、 Windows ならデフォルトは %USERPROFILE%\.m2\repository (Linux系のOSなら $HOME/.m2/repository)
    • settings.xml で別の場所を指定している場合は、そちら
ローカルリポジトリ
ローカルリポジトリ/
 |-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 オブジェクトが設定されているところを見つけきれなかった
  • ローカルリポジトリにインストールされたアーティファクトは、ローカルの別のプロジェクトで参照できるようになる
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>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> で宣言している
Foo.java
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-plugindeploy ゴール が実行される
  • 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 などのフォルダを作成している
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>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の中身
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 となっている
  • また、依存関係に指定していた 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 アプリケーションを実行する

Hello.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 して、コマンドライン引数を出力している実装
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>

  <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>
実行
> 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-pluginjava ゴール を使用すると、プロジェクトのビルド結果をクラスパスに入れて Java プログラムを実行できる
    • exec-maven-plugin には任意のコマンドを実行できる exec ゴール も用意されているが、ここでは割愛
  • mainClass パラメータで Main クラスを指定することで、 Java プログラムを実行できる
  • arguments パラメータで、コマンドライン引数を渡すことができる
    • commandlineArgs というパラメータでも引数を渡すことができるが、こちらは後述するコマンドラインから実行する場合に利用するためにある(たぶん)

コマンドラインから実行する

  • 最初の例は、あらかじめ pom.xml に実行の構成をすべて記述しておく必要がある
  • 場合によっては、コマンドラインで引数を色々変更しながら実行したくなることもあるかもしれない
  • その場合は、システムプロパティを介してゴールのパラメータを指定する方法をとるとやりやすい
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 スコープ

pom.xml
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.10</version>
</dependency>
  • スコープを指定していない場合、デフォルトで compile スコープが採用される(明示することも可能)
  • compile スコープは、コンパイルから実行まで、常にその依存関係が必要であることを表している

provided スコープ

pom.xml
<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 スコープ

pom.xml
<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 スコープ

pom.xml
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.6.1</version>
    <scope>test</scope>
</dependency>
  • test スコープは、テストソースのコンパイルと実行時にだけ使用する依存関係で使用する
  • main のソースコードのコンパイルや、実行時には使用できない
  • JUnit のような、テストのときだけ必要になる依存関係で使用する

system スコープ

pom.xml
<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つのプロジェクトが存在する
pom.xml(foo)
<?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
pom.xml(bar)
<?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 を確認する
effective-pomの確認(bar)
> 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 は特殊なので除外)

maven.jpg

  • が付いているのは、ゴールが該当するスコープの依存関係を参照・使用することを表している
  • war:war については、生成される war ファイル内に梱包されるかどうか、ということを意味している
  • それ以外は、クラスパスに設定されるかどうか、ということを意味している

依存関係をツリーで確認する

  • プロジェクトの持つ依存関係をツリー構造のグラフで表示して確認できる
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>

  <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-plugintree ゴール を実行すると、この推移的な依存関係を含めたすべての依存関係をツリー構造のグラフで確認できる
実行結果
> 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-corespring-jcl に依存していることが分かる
  • そして、 spring-jdbcspring-tx に依存していることがわかる
    • ちなみに、 spring-jdbcspring-beans, spring-core にも依存しているが、 spring-web 側と重複するため表示は省略されている
  • デフォルトでは全てのスコープの依存関係が出力されるが、 scope パラメータ で絞ることもできる

依存関係のjarを集める

  • プロジェクトが依存する jar ファイルの実物を手元にすべてかき集めたい、となることが稀によくある
  • これは、 maven-dependency-plugincopy-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)という仕組みが利用できる
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>

  <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> を指定する

複数のプロファイルを指定して実行する

pom.xml
<?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>
  • foobar という2つのプロファイルを宣言している
  • message は両方のプロファイルで宣言して重複するようにしている
実行結果
> mvn -P bar,foo antrun:run
...
     [echo] foo = FOO
     [echo] bar = BAR
     [echo] message = bar profile!!
  • プロファイルを複数指定する場合は、カンマ区切りで id を並べる
  • 重複する要素は、 pom.xml 上で後に来る方が採用されるっぽい(プロファイルの指定順ではなさそう)

プロファイルをデフォルトで有効にする

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 のプロファイルは無効になる

条件が満たされたときだけプロファイルを有効にする

システムプロパティが宣言されていたら有効にする

pom.xml
<?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 のバージョンを条件にする

pom.xml
<?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 のバージョンと比較される
  • なお、比較対象となる Java のバージョンは、システムプロパティ java.version で取得された値が使用されている
  • <jdk>!11</jdk> のように、先頭に ! をつけると条件を否定できる

OS を条件にする

pom.xml
<?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> を設定している
実行結果(Windows)
> mvn antrun:run
...
     [echo] message = Windows profile!!
実行結果(Linux)
> mvn antrun:run
...
     [echo] message = Linux profile!!
  • 実行時の OS によって、適用されるプロファイルが切り替わっている
  • <os> を使用すると、実行時に OS をプロファイル適用の条件に使用できる
  • <name> は、 OS の名前を条件にしている
    • OS の名前はシステムプロパティの os.name で取得できる値が使用されている
    • 大文字・小文字の区別はないっぽい
  • 名前以外にも、次の条件を指定できる
  • これらの条件を複数指定した場合は、全て AND 条件で結合される
  • 今の環境がどういう値になるのかは、 maven-enforcer-plugindisplay-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

ファイルの有無を条件にする

pom.xml
<?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 とか書いていると、うまく判定されない

使用できるプロファイルを確認する

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)

アクティブなプロファイルを確認する

実行結果
> 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> などの条件設定が可能なことから想像できるように、プロファイルの本来の目的は、ビルドを行う環境の差異を吸収することにあると思う
    • したがって、それ以外の用途で用いるのは極力避けたほうが良い気がする(私見)
  • 例えば、開発用・検証環境用・本番環境用に使用する設定ファイルを切り替えるような方法では、プロファイルは使うべきではないのかもしれない

プラグインを自作する

  • プラグインは自作できる
  • org.apache.maven.pluginscom.codehaus.mojo にあるプラグインで目的が達成できない場合は、自分でプラグインを作ることになる

Hello World

pom.xml
<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 でコンパイルしようとすると、このプラグインのバージョンを新しくしておかないとエラーになるっぽい?
HelloMojo.java
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 フェーズを実行して、作成したプラグインをローカルリポジトリにインストールする
  • インストールしたプラグインを、他のプロジェクトで使用してみる
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>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)

pom.xml
...
  <groupId>example</groupId>
  <artifactId>hello-maven-plugin</artifactId>
  <version>1.0.0</version>
...
  • 自作プラグインの artifactId は、 XXX-maven-plugin とするのが慣例となっている
  • このパターンの artifactId にしておくと、 XXX の部分が自動的にプラグインのプレフィックスとしてメタ定義が作成される
  • 試しに、作成された jar ファイルの中を確認する
jarの中身
hello-1.0.0.jar/
 |-example/
 `-META-INF/
   |-MANIFEST.MF
   `-maven/
     |-example/
     `-plugin.xml
  • この 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 という用語が現れる
  • その場合は、ゴールを実装したクラスのこと、と思えばいいと思う

パラメータを定義する

HelloMojo.java
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 は定義していない
pom.xml(別プロジェクト)
<?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 フィールドを、パラメータとして指定できるようになっている
  • ちなみに、この状態だとシステムプロパティで値を指定することはできない

システムプロパティでパラメータを指定できるようにする

HelloMojo.java
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);
    }
}
  • @Parameterproperty に、システムプロパティで指定するときの名前を設定する
pom.xml(別プロジェクト)
...
      <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!!
  • システムプロパティ経由で値を設定できるようになった

パラメータのデフォルト値を設定する

HelloMojo.java
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 で、そのパラメータのデフォルト値を宣言できる
pom.xml(別プロジェクト)
...
      <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 で設定した値が採用されているのがわかる

デフォルト値に式を使用する

HelloMojo.java
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>)も参照できる
pom.xml(別プロジェクト)
<?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 だけでなく様々な型で宣言できる
HelloMojo.java
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;
    }
}
  • 様々な型でパラメータを定義している
pom.xml(別プロジェクト)
<?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}
...
  • intlong, 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 も同じように使用できる

デフォルトのフェーズを紐付ける

HelloMojo.java
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!!");
    }
}
  • @MojodefaultPhase で、デフォルトのフェーズを指定できる
  • フェーズの指定には、 LifecyclePhase 列挙型を使用する
  • ここでは validate フェーズに紐づけている
pom.xml(別プロジェクト)
...
      <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/
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>
実行結果
> mvn clean
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ hello ---
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
フォルダ構成(実行後)
`-pom.xml
  • maven-clean-pluginclean ゴール を実行すると、プロジェクトの生成物をすべて削除できる
    • ここで指定しているのは clean フェーズ
    • clean ゴールは、デフォルトで clean フェーズに紐付いている(ややこしい)
  • デフォルトでは、次のフォルダが削除対象となっている
    • ${project.build.directory}
    • ${project.build.outputDirectory}
    • ${project.build.testOutputDirectory}
    • ${project.reporting.outputDirectory}
  • 特に上記フォルダのパスを変更していないのであれば、 ${project.basedir}/target が削除される

削除対象のフォルダを追加する

フォルダ構成
|-pom.xml
|-target/
`-foo/
pom.xml
<?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> でファイルを絞ることもできるっぽい(試してない)

参考兼公式ドキュメント目次

どこになんの情報があるのか分かりにくすぎるので整理してみる

  1. Maven 1.0 が 2004 年、Maven 2.0 が 2005 年、Maven 3.0 が 2010 年(Maven – Maven Releases History) 

  2. %USERPROFILE% はユーザのホームを指す Windows の環境変数(Linux とかなら $HOME のこと) 

  3. <modelVersion> も Super POM にあるやん」って思うけど、これは残念ながら継承されないもよう(消すとエラーになる) 

  4. 厳密には compiler::::compile のような指定の場合もプレフィックス指定と判定されるけど、ここはわかりやすさを優先してコロンの数としている(詳しくは MojoDescriptorCreator の実装を参照) 

  5. <release> はリリースバージョン内での最新なのに対して、 <latest> はスナップショットも含めて最新のバージョンを指している 

  6. 厳密には settings.xml<pluginGroups> で検索対象の groupId を追加できる(参考:Introduction to Plugin Prefix Resolution) 

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む