- 投稿日:2020-04-07T22:58:29+09:00
#1 世知辛い初心者エンジニアの壁 ~エンジニアになりたい君へ~
初投稿になります。あつぎです?
未経験新卒で入社したものの、初っ端から自宅学習を強いられております。
「あっ、これ前にやったやつだ!」みたいなこともなく、事前に自分で勉強していたjavaやマークアップ言語、Web系言語も意味をなしておりません。
まず初心者の方に伝えたいのは、入社する会社が
【SIer】なのか【Web系】なのか知っておきましょう。
なんとなく開発したいは、悪くもないですが、良くもないです。はい。■C言語めちゃくちゃムズイ
題名の通りです。
研修でC言語を学ぶのは「普通」なのでしょうか?
挫折率の高い言語を研修にするのは、なんとも言えない気持ちです。開発環境はEclipse IDE(統合開発環境)で作業してたのですが、
Javaのようにサクサク実行ができず詰んでおります
■詰んでるところ
プログラミングというのは、
❶コードを書いて
❷コンパイルして(PCが読める言葉に翻訳して)
❸実行結果(画面に表示される)
のですが、Eclipseの環境だと、なぜかコンパイルできずに死亡しております…。
配布資料を読んでも、バイナリー作成されず撃沈。
Windowsなのでコマンドプロンプトで実行するも、Eclipseで動かなきゃ意味がなし。
今妥協してvisualStudio(別の開発環境)でしてみるつもりです。
同じく、EclipseでC言語、ビルドしてもバイナリーできないよ!って躓く初心者いませんかね。
私はいったん放置して、別の手段で試してみます。
では、またの
- 投稿日:2020-04-07T19:23:49+09:00
ウイルス性肺炎と診断されたエンジニアの約2か月の在宅勤務記。
序:某渡辺さんの手記を読んだ。
4月1日実名公開の『これで「軽症」と言うのか。新型コロナ感染で入院中、渡辺一誠さんの手記』を読んだ。もちろん存じ上げない方だが六本木勤務らしい。いろいろと辛そう。。緊急宣言が出されることで、IT業界の方々も、これからいろいろな影響を受けることがあることだろう。
ということで、参考になればと、参考情報として、2月下旬に自室で悶絶(もんぜつ)しつつも保健所で門前払いされたのち3週間後に、自費で受けたCTスキャンでウイルス性肺炎と診断された私(中年♂)のこの二月ほどを振り返るポエムを書くことにした。恥ずかしいのだけれども。。
状況としては、病状が悪かったはじめの1月は在宅でいろいろとやらかした。回復後の1月弱で、いくつかを取り戻せたと思っている。勤怠については、(理解ある勤務先のおかげで)2か月間皆勤とさせてもらっている。(付記) CTによる診断と血液検査
(その他の検査での陽性反応を経て別途の投薬を受けるも)発熱してから1月たっても呼吸音がおかしいとのことで(通院4先目の病院で)CTスキャンをしてもらったところ、ウイルスが住み着く部位(以下のようなところらしい)に肺炎が残っているとのことで、ウイルス性肺炎と診断される(以下のような断面図を何枚か見ながら説明を受ける)。 => 免疫系のフルスキャン版の血液検査をするから、支払いヨロピクね、と、言われ、採血される。
=> 医師から受けたアドバイスは割合と有り難かった。...結果、お金は減ったが、現在は完治と思われ。今、なんとなく具合が悪い人へ。
...緊急なんちゃら宣言が出た今となってては、疑わしい体調であるとしても、コロナの検査に向かう際に業務が継続できる装備(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さんなんだからこうしたエンジニア人材問題、ビジネスとしてなんとかしてくれないかなと思ったり。あ、自分でサービス作ってみようかなと妄想してみたり。
- 投稿日:2020-04-07T18:05:20+09:00
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 を採用するという手もあると思います。
以下は参考文献程度に。
- 投稿日:2020-04-07T17:57:43+09:00
Jersey+Spring FrameworkでRESTfulAPIの最小構成サンプル
こちらの記事の続きです。
Mavenでマルチモジュール構成にする(Jersey RESTful)
https://qiita.com/kasa_le/items/db0d84e3e868ff14bc2b目的
JserseyとSpring 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.javapublic interface TransactionBo { String save(); }実装クラス
transaction/impl/TransactionBoImpl.javapublic class TransactionBoImpl implements TransactionBo { public String save() { return "Jersey + Spring example"; } }4.サービスクラスを作成
TransactionBoImpl
をSpringにDIしてもらいますが、@Autowired
アノテーションは使いません。
コンストラクタインジェクションを使います。
(コンストラクタインジェクションが推奨のようです。詳しくは末尾の参考サイトから)rest/PaymentService.javaimport 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 Resource
のリンクをクリックすると、rest/payment/mkyong
のGET
メソッドが叩かれ、戻り値が表示されます。
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.javapackage 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.javapublic 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-springProgrammers: 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/233141JUnitでテストするときにも、DIが動作するようにするには
https://wikiwiki.jp/webapp/Spring/JUnit
- 投稿日:2020-04-07T15:05:36+09:00
XMLDecoder反序列化分析
简介
XMLDecoder是java自带的以SAX方式解析xml的类,其在反序列化经过特殊构造的数据时可执行任意命令。在Weblogic中由于多个包
wls-wast
、wls9_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)其中
this.handler
为DocumentHandler
到这里进入
com.sun.beans.decoder.DocumentHandler#parse
圈住的代码其实和我们写的
DemoHandler
里一模一样,通过SAXParserFactory
工厂创建了实例,进而newSAXParser
拿到SAX解析器,调用parse
解析,那么接下来解析的过程,我们只需要关注DocumentHandler的几个事件函数就行了。在
DocumentHandler
的构造函数中指定了可用的标签类型
对应了
com.sun.beans.decoder
包中的几个类
在startElement中首先解析
java
标签,然后设置Owner和Parent。
this.getElementHandler(var3)
对应的就是从构造方法中放入this.handlers
的hashmap取出对应的值,如果不是构造方法中的标签,会抛出异常。
然后解析
object
标签,拿到属性之后通过addAttribute()设置属性
在addAttribute()没有对class属性进行处理,抛给了父类
com.sun.beans.decoder.NewElementHandler#addAttribute
会通过findClass()去寻找
java.lang.ProcessBuilder
类
赋值完之后跳出for循环进入
this.handler.startElement()
,不满足条件。
接下来解析
array
标签,同样使用addAttribute对属性赋值
同样抛给父类
com.sun.beans.decoder.NewElementHandler#addAttribute
处理
继续抛给父类
com.sun.beans.decoder.NewElementHandler#addAttribute
最后进入
com.sun.beans.decoder.ArrayElementHandler#startElement
因为ArrayElementHandler类没有0个参数的getValueObject()重载方法,但是它继承了NewElementHandler,所以调用
com.sun.beans.decoder.NewElementHandler#getValueObject()
这个getValueObject重新调用
ArrayElementHandler#getValueObject
两个参数的重载方法
ValueObjectImpl.create(Array.newInstance(var1, this.length))
创建了长度为1、类型为String的数组并返回,到此处理完array标签。接着处理void,创建VoidElementHandler,设置setOwner和setParent。
调用父类
com.sun.beans.decoder.ObjectElementHandler#addAttribute
设置index属性
继续解析string标签,不再赘述。
解析完所有的开始标签之后,开始解析闭合标签,最开始就是,进入到endElement()
StringElementHandler没有endElement(),调用父类ElementHandler的endElement()
调用本类的getValueObject()
设置value为calc。通过父类的addAttribute将this.method赋值为start
调用
endElement
,VoidElementHandler
类没有,所以调用父类ObjectElementHandler.endElement
调用
NewElementHandler
类无参getValueObject
然后调用
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()
继续调用NewElementHandler父类ElementHandler的getContextBean()
会调用
this.parent.getValueObject()
也就是ObjectElementHandler类,而ObjectElementHandler没有无参getValueObject()方法,会调用其父类NewElementHandler的方法
然后将值赋值给value返回最终var3的值为
java.lang.ProcessBuilder
。
通过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不能获得返回值。
- 投稿日:2020-04-07T01:44:13+09:00
初心者がスッキリわかるシリーズで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アプリケーションの知識・技術は奥がとてつもなく深くて、この本ではさわりの部分だけをやってるんだな、となんとなく感じた。
- 投稿日:2020-04-07T01:06:12+09:00
【JAVA関連】【短期】Qiitaの今読んでおくべき記事100選【毎日自動更新】
ページ容量を増やさないために、不具合報告やコメントは、説明記事 に記載いただけると助かります。
順位 記事名
________________________________________ユーザ 投稿日付
更新日付LGTM1 1 SwaggerでRESTful APIの管理を楽にする disc99 16/10/10
18/02/021244
452 30分で覚えるKotlin文法 k5n 16/02/10
18/03/30838
503 Kotlin スコープ関数 用途まとめ ngsw_taro 15/09/29
18/05/16746
504 スマホアプリ開発者のための2019年動向まとめ keidroid 20/01/17
20/01/24472
4725 JavaプログラマのためのKotlin入門 koher 17/05/18
17/12/221351
246 モダンなJava開発ガイド (2018年版) yoichiwo7 18/08/24
18/09/011112
427 忙しい人のためのIntelliJ IDEAショートカット集(´-`) yoppe 15/03/24
19/07/121739
08 Java8のラムダ式を理解する sano1202 17/05/22
19/07/29433
369 【超初心者向け】Maven超入門 tarosa0001 17/07/01
18/10/31454
2810 Java 14新機能まとめ nowokay 20/02/13
20/04/05417
41711 Kotlin のコレクション使い方メモ opengl-8080 16/06/04
19/02/14424
4412 SpringBootに入門する為の助走本(随時更新) sugaryo 18/05/18
20/01/30311
3513 javaプログラマー向け学習のための本(新人から5年めくらいまで)を考えてみた bonybeat 16/06/11
19/08/091060
3814 Java初心者時代にコードレビューで指摘された悪しき習慣 gengogo5 18/04/23
18/04/301034
1415 KotlinとJavaができる人向けDart速習 kikuchy 18/09/18
20/01/08297
4016 Android Architecture Components 初級 ( MVVM + LiveData + Coroutines 編 ) Tsutou 18/05/30
19/11/10342
2917 MacのBrewで複数バージョンのJavaを利用する + jEnv seijikohara 17/10/03
20/03/21329
2518 フロントエンド、サーバサイド、インフラの具体例 shuntaro_tamura 14/10/13
16/03/161138
019 Thymeleafチートシート NagaokaKenichi 16/04/17
18/04/23437
2620 Spring Boot で Thymeleaf 使い方メモ opengl-8080 17/08/14
17/08/14309
3021 TOMCAT殺害事件 enoshiman 20/01/08
20/01/15393
6722 Spring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解する kazuki43zoo 16/05/13
19/07/22526
1623 2019年に注目すべきWebテクノロジー6選 rana_kualu 19/02/04
19/02/05898
624 これだけは覚えたい、ユニットテストを書くための4つのデザイン koduki 17/01/15
18/02/20820
1425 Spring Framework / Spring Bootに入門する人はまずこの資料を読もう! #jsug suke_masa 19/08/29
20/02/07441
3926 [初心者]オブジェクト指向でなぜつくるのか IZUMIRU 19/06/02
19/06/02480
1327 OutOfMemoryError の調べ方 opengl-8080 16/03/13
18/04/05816
1628 Gradle の compile, api, implementation とかについて opengl-8080 18/05/25
18/05/25239
2429 `get()`を使うな ~ 敗北者の Optional BlueRayi 20/03/30
20/04/07199
19930 JavaプログラマがKotlinでつまづきがちなところ koher 17/05/22
17/12/29630
1231 令和時代に「Spring入門」「Spring徹底入門」を読むとき気をつけるべきN個のこと suke_masa 19/12/31
20/02/12286
3632 Seleniumで要素を選択する方法まとめ VA_nakatsu 18/02/08
19/01/15177
2233 あっと驚かせるJavaプログラミング(をやめよう) tatesuke 17/06/13
20/03/03975
534 最初に押さえておきたいKotlin言語仕様 ssuzaki 16/04/06
20/01/24231
2635 JavaによるWebアプリケーションの仕組みをざっくり説明 mkdkkn 18/10/11
18/10/11169
3136 JDK、Oracle JDK、OpenJDK、Java SEってなに? nowokay 18/06/25
19/06/20396
1437 デザインパターン「Factory Method」 shoheiyokoyama 16/02/20
17/03/27261
2338 認証におけるJWTの利用について shnmorimoto 17/12/14
17/12/15226
1839 Kotlin の Coroutine を概観する kawmra 17/11/16
19/03/01201
1740 GoFのデザインパターンまとめ i-tanaka730 19/03/28
19/03/2897
5641 JDKの長期商用サポート(LTS)の提供ベンダー比較(無償利用についても言及あり) u-tanick 18/09/26
19/10/06213
1642 アジャイル開発を支えるためのCI/CD newta 16/12/02
16/12/02240
1843 Android Kotlin codelab courseで、今から最新のAndroidアプリ開発を始めよう takahirom 19/09/29
19/11/27334
2444 Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ disc99 14/11/09
19/03/152220
045 [Android] ConstraintLayout レイアウト逆引きまとめ tktktks10 18/08/30
19/09/18126
2846 新人プログラマに知ってもらいたいRabbitMQ初心者の入門の入門 gambaray 16/02/25
16/02/26410
1547 正規表現の基本 sea_ship 14/11/12
17/12/21722
048 Javaのサポートについてのまとめ2018 nowokay 18/05/07
19/12/08522
549 新人研修でドヤ顔で披露したらウケたEclipseのショートカット集 arai-wa 14/10/07
19/03/261854
050 削除済(ID:8595ca60a5c8d31bbe37) k-kagurazaka@github 17/12/02
18/03/29346
651 Kotlinを勉強する際に役立つリソース集 r-kaga 18/08/17
19/06/30174
1552 Javaのパターンメモ nowokay 18/12/26
19/04/22105
953 意外と知らないIntelliJ IDEAのGit管理機能いろいろ(´-`) yoppe 15/11/17
15/11/19419
1454 Spring MVC コントローラの引数 MizoguchiKenji 17/07/18
18/03/22167
2655 図で理解する Kotlin Coroutine kawmra 19/01/28
19/02/03292
1456 JavaのGCの仕組みを整理する e_tyubo 19/03/31
19/03/31331
2257 BCryptのすすめ ponkotuy 16/12/12
18/08/27202
1758 猿でも作れるサーバサイドKotlin入門【Spring Boot, Doma2】Part1 shierote 18/12/08
19/09/10157
1959 【Spring Data JPA】自動実装されるメソッドの命名ルール shindo_ryo 16/07/11
20/01/29205
1560 段階的に理解する Java 例外処理 ts7i 18/12/21
18/12/26170
1561 IntelliJ IDEA 入門 opengl-8080 16/04/06
18/04/02390
1662 Java8からJava11への変更点 nowokay 18/12/14
20/02/02174
2463 Spring での責務についてまず見てほしい一枚の絵 yo1000 16/04/09
18/08/07239
1564 「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した mizunowanko 16/08/13
16/08/141364
1565 Androidで個人的によく使うlayout系設定チートシート k_keisuke 17/08/04
20/03/03195
2166 Java Stream APIをいまさら入門 takumi-n 16/03/31
17/05/27198
1467 Awesome Java : 素晴しい Java フレームワーク・ライブラリ・ソフトウェアの数々 hatai 17/02/07
19/11/01503
1368 【初心者向け】10分で絶対にわかる!JavaBeansとは s_hino 17/03/29
17/04/04140
1769 Javadoc ドキュメンテーションコメントの書き方 maku77 14/10/04
19/07/16772
070 RecyclerViewの基本 naoi 17/04/28
18/08/17161
1671 軽い気持ちでLinkedListを使ったら休出する羽目になった話 neko_machi 18/02/18
18/02/28767
1072 経験ゼロでもできるプログラミング現場の単体テスト disc99 14/02/19
16/11/03727
073 Spring Bootの外部設定値の扱い方を理解する kazuki43zoo 16/09/11
17/12/22264
1274 javaを使ってWebアプリを作るまでに結局なにが必要なのか。仕組みと学習に必要なものをザックリ説明 shimatter 18/11/15
18/12/1090
2575 エニグマを実装してみた opengl-8080 19/08/15
19/08/18354
376 Logback 使い方メモ opengl-8080 15/10/26
15/12/28350
1277 最近のAndroid開発でよく使われているっぽいライブラリまとめ(2019/11) gericass 19/05/26
19/11/19280
1178 Java ジェネリクスのポイント pebblip 15/07/20
15/07/21391
979 Gradle を完全に理解した人が、何も分からなくなるための第一歩 opengl-8080 19/11/23
19/11/23227
1080 Spring BootのAutoConfigureの仕組みを理解する kazuki43zoo 16/09/06
16/09/06272
1081 Spring Security 使い方メモ 認証・認可 opengl-8080 17/04/27
18/09/06193
1082 実例によるkotlinx.coroutinesガイド(日本語訳) pljp 17/06/12
19/09/09214
283 [Android] 10分で作る、Navigationによる画面遷移 tktktks10 18/09/15
19/10/29119
1484 脆弱性診断ツール OWASP ZAP vs 脆弱性だらけのWebアプリケーションEasyBuggy tamura__246 17/09/11
17/09/12224
1085 ちょっといいJavaコードを書こう nishemon 15/11/22
18/12/22843
1086 Spring MVC(+Spring Boot)上での静的リソースへのアクセスを理解する kazuki43zoo 16/05/05
17/02/12265
1287 Kotlin/Native を Android/iOS アプリ開発に導入しよう irgaly 18/10/12
18/10/17407
1388 「Java8からJava11」で何が起きたのか、どう環境構築すればいいのか to-lz1 20/01/10
20/03/1370
6589 メモリリーク、デッドロック、リダイレクトループ、JVMクラッシュ...バグだらけのWebアプリケーションを使ってバグを理解する tamura__246 17/02/14
18/01/221094
690 ジャバの異常な愛情 またはSpringはいかにしてモダンであることを止めて時代遅れになったのか psycho 19/10/03
20/03/13308
1991 mybatis-spring-boot-starterの使い方 kazuki43zoo 16/04/02
19/07/16188
1392 KubernetesネイティブなJavaフレームワーク Quarkus について調査してみた件 mamomamo 19/03/19
19/03/20126
1693 Spring Boot 使い方メモ opengl-8080 15/05/02
17/08/12821
094 はじめてのKotlin。Javaと比較してみた hituziando 16/04/02
19/10/24137
1395 【Android Studio】ショートカット大好き人間がおくるショートカット集【IntelliJ IDEA】 arai-wa 17/03/27
17/08/19166
1696 図で理解するJavaのクラスとインスタンス hysdsk 17/07/06
17/08/09144
597 翻訳: Kotlinベストプラクティス『Idiomatic Kotlin. Best Practices』 takahirom 17/07/02
19/11/20367
1098 Java 8 "Optional" ~ これからのnullとの付き合い方 ~ shindooo 14/09/13
18/05/04598
099 【Java】formとentityとdtoの違いって?【Bean】 mtanabe 17/12/01
17/12/01105
16100 Spring Security with Spring Boot 2.0で簡単なRest APIを実装する rubytomato@github 18/04/10
18/05/10119
12
1行目が総数。2行目が直近3ヵ月。 ↩
- 投稿日:2020-04-07T01:06:09+09:00
【JAVA関連】【長期】Qiitaの今読んでおくべき記事100選【毎日自動更新】
ページ容量を増やさないために、不具合報告やコメントは、説明記事 に記載いただけると助かります。
順位 記事名
________________________________________ユーザ 投稿日付
更新日付LGTM1 1 忙しい人のためのIntelliJ IDEAショートカット集(´-`) yoppe 15/03/24
19/07/121739
1912 SwaggerでRESTful APIの管理を楽にする disc99 16/10/10
18/02/021244
2093 30分で覚えるKotlin文法 k5n 16/02/10
18/03/30838
2534 Kotlin スコープ関数 用途まとめ ngsw_taro 15/09/29
18/05/16746
2005 JavaプログラマのためのKotlin入門 koher 17/05/18
17/12/221351
1746 フロントエンド、サーバサイド、インフラの具体例 shuntaro_tamura 14/10/13
16/03/161138
1107 Java8のラムダ式を理解する sano1202 17/05/22
19/07/29433
1968 モダンなJava開発ガイド (2018年版) yoichiwo7 18/08/24
18/09/011112
1489 Kotlin のコレクション使い方メモ opengl-8080 16/06/04
19/02/14424
15610 【超初心者向け】Maven超入門 tarosa0001 17/07/01
18/10/31454
17011 javaプログラマー向け学習のための本(新人から5年めくらいまで)を考えてみた bonybeat 16/06/11
19/08/091060
15412 Javaを使うなら知っておきたい技術、フレームワーク、ライブラリ、ツールまとめ disc99 14/11/09
19/03/152220
8213 Thymeleafチートシート NagaokaKenichi 16/04/17
18/04/23437
12814 正規表現の基本 sea_ship 14/11/12
17/12/21722
7615 Spring MVC(+Spring Boot)上でのリクエスト共通処理の実装方法を理解する kazuki43zoo 16/05/13
19/07/22526
13716 新人研修でドヤ顔で披露したらウケたEclipseのショートカット集 arai-wa 14/10/07
19/03/261854
9217 SpringBootに入門する為の助走本(随時更新) sugaryo 18/05/18
20/01/30311
19018 Java初心者時代にコードレビューで指摘された悪しき習慣 gengogo5 18/04/23
18/04/301034
5419 これだけは覚えたい、ユニットテストを書くための4つのデザイン koduki 17/01/15
18/02/20820
13220 MacのBrewで複数バージョンのJavaを利用する + jEnv seijikohara 17/10/03
20/03/21329
15821 OutOfMemoryError の調べ方 opengl-8080 16/03/13
18/04/05816
10722 Android Architecture Components 初級 ( MVVM + LiveData + Coroutines 編 ) Tsutou 18/05/30
19/11/10342
16623 Spring Boot で Thymeleaf 使い方メモ opengl-8080 17/08/14
17/08/14309
13024 JavaプログラマがKotlinでつまづきがちなところ koher 17/05/22
17/12/29630
8625 あっと驚かせるJavaプログラミング(をやめよう) tatesuke 17/06/13
20/03/03975
5226 2019年に注目すべきWebテクノロジー6選 rana_kualu 19/02/04
19/02/05898
12027 KotlinとJavaができる人向けDart速習 kikuchy 18/09/18
20/01/08297
16928 Javadoc ドキュメンテーションコメントの書き方 maku77 14/10/04
19/07/16772
7129 経験ゼロでもできるプログラミング現場の単体テスト disc99 14/02/19
16/11/03727
7630 新人プログラマに知ってもらいたいRabbitMQ初心者の入門の入門 gambaray 16/02/25
16/02/26410
8631 デザインパターン「Factory Method」 shoheiyokoyama 16/02/20
17/03/27261
9332 最初に押さえておきたいKotlin言語仕様 ssuzaki 16/04/06
20/01/24231
10633 意外と知らないIntelliJ IDEAのGit管理機能いろいろ(´-`) yoppe 15/11/17
15/11/19419
6934 アジャイル開発を支えるためのCI/CD newta 16/12/02
16/12/02240
8635 スマホアプリ開発者のための2019年動向まとめ keidroid 20/01/17
20/01/24472
47236 削除済(ID:8595ca60a5c8d31bbe37) k-kagurazaka@github 17/12/02
18/03/29346
7837 Gradle の compile, api, implementation とかについて opengl-8080 18/05/25
18/05/25239
11338 Javaのサポートについてのまとめ2018 nowokay 18/05/07
19/12/08522
4939 Spring Boot 使い方メモ opengl-8080 15/05/02
17/08/12821
5840 JDK、Oracle JDK、OpenJDK、Java SEってなに? nowokay 18/06/25
19/06/20396
10841 Java 8 "Optional" ~ これからのnullとの付き合い方 ~ shindooo 14/09/13
18/05/04598
5642 [初心者]オブジェクト指向でなぜつくるのか IZUMIRU 19/06/02
19/06/02480
48043 IntelliJ IDEA 入門 opengl-8080 16/04/06
18/04/02390
6144 認証におけるJWTの利用について shnmorimoto 17/12/14
17/12/15226
10045 Kotlin の Coroutine を概観する kawmra 17/11/16
19/03/01201
11146 Java ジェネリクスのポイント pebblip 15/07/20
15/07/21391
6347 Seleniumで要素を選択する方法まとめ VA_nakatsu 18/02/08
19/01/15177
12548 Spring MVCのコントローラでの戻り値いろいろ tag1216 14/12/17
14/12/17510
6249 【Spring Data JPA】自動実装されるメソッドの命名ルール shindo_ryo 16/07/11
20/01/29205
7950 Java 14新機能まとめ nowokay 20/02/13
20/04/05417
41751 Gradle使い方メモ opengl-8080 13/12/07
19/02/26681
5352 BCryptのすすめ ponkotuy 16/12/12
18/08/27202
6653 Spring での責務についてまず見てほしい一枚の絵 yo1000 16/04/09
18/08/07239
7654 「なぜDI(依存性注入)が必要なのか?」についてGoogleが解説しているページを翻訳した mizunowanko 16/08/13
16/08/141364
7155 Spring Framework / Spring Bootに入門する人はまずこの資料を読もう! #jsug suke_masa 19/08/29
20/02/07441
44156 もう参照渡しとは言わせない mdstoy 14/04/24
19/05/03419
3257 Spring Bootの外部設定値の扱い方を理解する kazuki43zoo 16/09/11
17/12/22264
6658 Java Stream APIをいまさら入門 takumi-n 16/03/31
17/05/27198
7559 Java8の日時APIはとりあえずこれだけ覚えとけ tag1216 15/05/17
17/07/19496
4360 Logback 使い方メモ opengl-8080 15/10/26
15/12/28350
6661 JDKの長期商用サポート(LTS)の提供ベンダー比較(無償利用についても言及あり) u-tanick 18/09/26
19/10/06213
12362 メモリリーク、デッドロック、リダイレクトループ、JVMクラッシュ...バグだらけのWebアプリケーションを使ってバグを理解する tamura__246 17/02/14
18/01/221094
2463 Spring BootのAutoConfigureの仕組みを理解する kazuki43zoo 16/09/06
16/09/06272
6564 JavaによるWebアプリケーションの仕組みをざっくり説明 mkdkkn 18/10/11
18/10/11169
12265 Awesome Java : 素晴しい Java フレームワーク・ライブラリ・ソフトウェアの数々 hatai 17/02/07
19/11/01503
7266 Spring MVC コントローラの引数 MizoguchiKenji 17/07/18
18/03/22167
7867 実例によるkotlinx.coroutinesガイド(日本語訳) pljp 17/06/12
19/09/09214
6168 軽い気持ちでLinkedListを使ったら休出する羽目になった話 neko_machi 18/02/18
18/02/28767
4969 削除済(ID:98ffbeaee42d30cca4dc) lrf141 15/07/12
16/10/23310
5270 ちょっといいJavaコードを書こう nishemon 15/11/22
18/12/22843
6871 Kotlinを勉強する際に役立つリソース集 r-kaga 18/08/17
19/06/30174
11772 Scala入門時に役立つ情報まとめ nesheep5 15/11/09
18/03/22507
3573 Spring MVC(+Spring Boot)上での静的リソースへのアクセスを理解する kazuki43zoo 16/05/05
17/02/12265
5874 RecyclerViewの基本 naoi 17/04/28
18/08/17161
7275 Androidで個人的によく使うlayout系設定チートシート k_keisuke 17/08/04
20/03/03195
7176 Groovyを知らない人のためのbuild.gradle読み書き入門 opengl-8080 14/12/14
19/01/13506
5777 【初心者向け】10分で絶対にわかる!JavaBeansとは s_hino 17/03/29
17/04/04140
8278 Spring Security 使い方メモ 認証・認可 opengl-8080 17/04/27
18/09/06193
6279 図で理解する Kotlin Coroutine kawmra 19/01/28
19/02/03292
11980 脆弱性診断ツール OWASP ZAP vs 脆弱性だらけのWebアプリケーションEasyBuggy tamura__246 17/09/11
17/09/12224
5281 [Android] ConstraintLayout レイアウト逆引きまとめ tktktks10 18/08/30
19/09/18126
10582 Kotlin文法 - 関数とラムダ k5n 16/01/28
16/01/29186
4783 TOMCAT殺害事件 enoshiman 20/01/08
20/01/15393
39384 mybatis-spring-boot-starterの使い方 kazuki43zoo 16/04/02
19/07/16188
6085 幸せな非同期処理ライフを満喫するための基礎から応用まで KeithYokoma 15/03/24
16/11/16620
3586 はじめてのKotlin。Javaと比較してみた hituziando 16/04/02
19/10/24137
5787 業務システムにおけるロールベースアクセス制御 kawasima 15/12/02
15/12/02287
4988 段階的に理解する Java 例外処理 ts7i 18/12/21
18/12/26170
10989 JavaのGCの仕組みを整理する e_tyubo 19/03/31
19/03/31331
9190 プログラミング言語を作る。1時間で。 shuetsu@github 16/09/27
16/10/03645
4691 猿でも作れるサーバサイドKotlin入門【Spring Boot, Doma2】Part1 shierote 18/12/08
19/09/10157
10792 Jackson使い方メモ opengl-8080 14/08/09
14/08/09551
3293 オブジェクト指向エクササイズをやってみる opengl-8080 16/03/19
16/05/28371
3594 Android Kotlin codelab courseで、今から最新のAndroidアプリ開発を始めよう takahirom 19/09/29
19/11/27334
33495 Scalaの記号みたいな奴らなんなの harry0000 16/05/18
19/11/12245
5096 図で理解するJavaのクラスとインスタンス hysdsk 17/07/06
17/08/09144
6997 MyBatis 使い方メモ opengl-8080 16/01/03
16/01/03233
4598 Kotlin/Native を Android/iOS アプリ開発に導入しよう irgaly 18/10/12
18/10/17407
4599 Mockito 初めの一歩 mstssk 13/06/13
14/04/01444
29100 翻訳: Kotlinベストプラクティス『Idiomatic Kotlin. Best Practices』 takahirom 17/07/02
19/11/20367
47
1行目が総数。2行目が直近1年。 ↩
- 投稿日:2020-04-07T00:24:45+09:00
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
を入力して EnterBUILD SUCCESS
と表示されれば成功生成されたプロジェクトの確認
- カレントフォルダに
artifactId
で指定した名前と同じ名前のフォルダが作られている
- 上の例で作った場合は
hello-world
というフォルダが作られているhello-world
フォルダの中身は次のようになっているhello-worldフォルダの中身hello-world/ |-pom.xml `-src/ |-main/java/ | `-com/example/ | `-App.java `-test/java/ `-com/example/ `-AppTest.javapom.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.javapackage 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
は、そのプロジェクトのバージョンを指定するgroupId
とartifactId
によってプロジェクトが一意に特定され、さらにversion
によってバージョンが特定されることになる- この pom.xml に記述されていない設定は、基本的に Super POM から継承される3
- つまり、記述されていない
<repository>
の設定は、<url>https://repo.maven.apache.org/maven2</url>
が採用されていることになる- ただ、実は Super POM に記述されてないけどデフォルトで採用されている設定も存在している(
<packaging>
とか)
- そのへんの話はおいおい
開発バージョンを表すプレフィックス
- アーティファクトのバージョン番号について、決まった採番のルールはない(たぶん)
- ただし、末尾に
-SNAPSHOT
とついたバージョンについては、特別な意味がある-SNAPSHOT
プレフィックスが付いたバージョンは、それが開発中のバージョンであることを表している- 開発中のバージョンは、アーティファクト(jar)の中身が更新される可能性があることを表している
- 一方で、 SNAPSHOT が付いていないバージョンは基本的にリリースバージョンを表しており、中身が更新されない(というのがお約束)
- あくまでお約束なので、仕組みで縛られているわけではない
- 後述するリポジトリ(Nexus)の機能で、リリースバージョンは更新不可、SNAPSHOTは更新可にするような制御は可能
変数
pom.xml の中では、同じ値を複数の箇所で重複して記述してしまう問題を回避するため、変数を参照できるようになっている。
プロジェクトモデル変数
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()
は、そのプラグインのgroupId
とartifactId
をコロン (:
) でつなげた値を返すproject.build
で参照できる Build クラスは、このPluginContainer
を継承している- このように、一部のクラスには
List
をMap
形式に変換したプロパティを提供しているものがある各クラスのプロパティ
Model
から参照できる各クラスやプロパティの全体像をクラス図でまとめた。
- 赤線が継承
- 青線が単一の参照
- 緑線が 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 は、すべての処理がプラグイン(Plugin)によって行われる。
例えば、 Java のソースコードをコンパイルするための処理は maven-compiler-plugin によって提供されている。
Maven プロジェクト自身が提供する基本的なプラグインは、Maven – Available Plugins で一覧を確認できる。
プラグインのゴール(Goal)
- プラグインに定義された個々のタスク(処理)のことをゴール(Goal)と呼ぶ
- 使用したいプラグインがどのようなゴールを持つかは、それぞれのプラグインのドキュメントを参照する
- maven-jdeps-plugin に定義されているゴール
- maven-jar-plugin に定義されているゴール
ゴールの実行方法
プラグインのゴールを実行する方法は、大きく次の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-plugin
がartifactId
になる- 次は、
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>
)5 をversion
とする- 以上で決定した
groupId
,artifactId
,version
を、完全修飾名とする
groupId
:org.apache.maven.plugin
artifactId
:maven-jdeps-plugin
version
:3.1.2
プラグインを明示的に設定する
- ↑の方法はメタデータを元にプラグインのバージョンが決まっている
- おそらく新しいバージョンが出たら最新版を使うことになるが、動きを固定できないとビルドが不安定になる恐れがある
- したがって、通常はプロジェクトごとに使用するプラグインのバージョンを固定する
- また、↑の場合 prefix 指定ができるのは
groupId
がorg.apache.maven.plugin
かorg.codehaus.mojo
のいずれかの場合に限られる6
- これら以外の
groupId
のプラグインを prefix 指定で利用したい場合は、プラグインを明示的に設定する必要がある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-plugin
のplugin.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 と等しいプラグインの
groupId
とartifactId
が採用される- 最後に、
version
は次のように決まる
- pom.xml で指定されていれば、そのバージョンが採用される
- 指定されていない場合は、
artifactId
ごとのメタデータ(maven-metadata.xml
)から決定される(プラグイン指定なしの場合と同じ方法)artifactId と prefix の関係
- ここまでの説明で、
artifactId
と prefix には、本質的には関連がないことがわかる
maven-metadata.xml
の<prefix>
か、plugin.xml
の<goalPrefix>
で prefix が決まっている- しかし、実際に存在するプラグインたちには、
artifactId
と prefix に次のような関係がある
- Maven プロジェクトが提供している公式プラグインの場合
artifactId
がmaven-XXX-plugin
の場合、XXX
が prefix となる- その他のプラグインの場合
artifactId
がXXX-maven-plugin
の場合、XXX
が prefix となる- これらは、そういう命名ルールが推奨されている結果であって、この名前じゃないとプラグインが作れないわけではない
- 作ろうと思えば、この命名ルールとは全く異なる
artifactId
のプラグインを作ることもできる- ただし、
maven-XXX-plugin
という命名は、それが Maven の公式プラグインであること表すためのものなので、公式以外がこの命名を使うと商標侵害になるので要注意- 一般的でない命名をしたところで利用者が混乱するだけでメリットがないので、プラグインを自作するなら特別な理由がない限りは
XXX-maven-plugin
というartifactId
にしておくのが無難- ということで、
artifactId
と prefix は本質的には関連しないが、実用上は関連しているものと思って問題ない(と思う)
- なので、
artifactId
がfoo-maven-plugin
なら、 prefix はfoo
と思っていい(と思う)- 逆に、 prefix が
foo
なら、artifactId
はfoo-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
を指定している
plugin
パラメータの説明は ドキュメント を参照- このように、ゴールのパラメータの中にはシステムプロパティで値を渡すことができるものがある
- すべてのパラメータがシステムプロパティで指定できるわけではない
- あるパラメータがシステムプロパティで指定できるかどうかは、そのパラメータのドキュメントに 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>
で、plugin
にjdeps
を設定している実行結果(システムプロパティで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 プログラムをビルドするには、次のような処理が考えられる
- ソースコードをコンパイルする
- リソースファイルを収集する
- テストコードをコンパイルする
- テスト用のリソースファイルを収集する
- テストコードを実行する
- コンパイル結果とリソースファイルをまとめて、 jar などのアーカイブにパッケージングする
- アーカイブを所定の場所に格納する
- Maven では、これら1つ1つの処理をフェーズ(Phase)と呼ぶ
- そして、一連のフェーズのセットをライフサイクル(Lifecycle)と呼ぶ
組み込みのライフサイクル
- Maven には、標準で次の3つのライフサイクルが用意されている
default
clean
site
default ライフサイクル
default
ライフサイクルは、プロジェクトをビルドしてデプロイするまでのライフサイクルを定義しているdefault
ライフサイクルは、次のフェーズで構成されている
validate
initialize
generate-sources
process-sources
generate-resources
process-resources
compile
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources
test-compile
process-test-classes
test
prepare-package
package
pre-integration-test
integration-test
post-integration-test
verify
install
deploy
clean ライフサイクル
clean
ライフサイクルは、プロジェクトの成果物を削除するライフサイクルを定義しているclean
ライフサイクルは、次のフェーズで構成されている
pre-clean
clean
post-clean
site ライフサイクル
site
ライフサイクルは、プロジェクトの Web サイト生成のライフサイクルを定義しているsite
ライフサイクルは、次のフェーズで構成されている
pre-site
site
post-site
site-deploy
フェーズとプラグインのゴールの関係
- 各ライフサイクルのフェーズには、そのフェーズで実行するプラグインのゴールが紐付けられている
clean, site ライフサイクルの場合
- 例えば、
clean
ライフサイクルとsite
ライフサイクルでは、次のようにゴールが紐付けられているclean プラグイン
フェーズ プラグイン ゴール pre-clean
- - clean
maven-clean-plugin
clean
post-clean
- - site プラグイン
フェーズ プラグイン ゴール pre-site
- - site
maven-site-plugin
site
post-site
- - site-deploy
maven-site-plugin
deploy
- つまり、
clean
フェーズを実行すると、maven-clean-plugin
のclean
ゴールが実行されることになるdefault ライフサイクルの場合
default
ライフサイクルの場合、フェーズとゴールの紐付けは固定ではないdefault
ライフサイクルで実行されるゴールは、そのプロジェクトの packaging によって変化するpackaging
- packaging とは、そのプロジェクトをどのようにパッケージ化するかを決める設定値で、次のいずれかの値が指定できる
pom
jar
ejb
maven-plugin
war
ear
rar
- この packaging は、 pom.xml 上で次のように指定する
pom.xml<?xml version="1.0" encoding="UTF-8"?> <project ...> ... <packaging>jar</packaging> ... </project>
- ここでは、 packaging として
jar
を指定している- なお、
<packaging>
タグは pom.xml で省略することができる
- 省略した場合、デフォルトは
jar
になるpackaging ごとのゴール
default
ライフサイクルのフェーズに紐付けられているゴールは、 packaging によって次のように異なる- 一例として、
pom
とjar
に紐付けられたゴールは次のようになっている
- フェーズの数が多いので、何も紐付けられていないフェーズは除外している(
validate
など)- これら以外の packaging については Maven Core – Plugin Bindings for Default Lifecycle Reference などを参照
pom
フェーズ プラグイン ゴール install
maven-install-plugin
install
deploy
maven-deploy-plugin
deploy
jar
フェーズ プラグイン ゴール process-resources
maven-resources-plugin
resources
compile
maven-compiler-plugin
compile
process-test-resources
maven-resources-plugin
testResources
test-compile
maven-compiler-plugin
testCompile
test
maven-surefire-plugin
test
package
maven-jar-plugin
jar
install
maven-install-plugin
install
deploy
maven-deploy-plugin
deploy
フェーズを実行する
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
フェーズの前にはvalidate
やprocess-resources
,test-compile
といったフェーズが定義されている- 上の例では
maven-resources-plugin:resources
,maven-compiler-plugin:compile
, ... と実行されていっていることがわかる- あるフェーズが実行されると、そのフェーズに紐付いているゴールがすべて実行される
maven-resources-plugin:resources
ゴールはresources
フェーズに、
maven-compiler-plugin:compile
ゴールはcompile
フェーズに紐付けらているフェーズやゴールを複数指定して実行する
> mvn clean compiler:compile
- フェーズやゴールは、 mvn コマンドに複数指定して実行できる
- この場合の実行順序は、
clean
→compiler:compile
の順序になる- つまり、引数で指定した順番で実行されるようになっている
フェーズにゴールを紐付ける
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-plugin
のrun
ゴールを紐付けている<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-plugin
のjdkinternals
とtest-jdkinternals
をverify
フェーズに紐づけている- 1つのフェーズに複数のゴールが紐付けられている場合、それらのゴールは pom.xml 上で宣言されている順番で実行される7
- つまり、↑の設定の場合は
jdkinternals
→test-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-cli
とfoo
の、2つの<id>
で<execution>
を定義している実行結果> mvn antrun:run ... [echo] Hello default-cli!! > mvn antrun:run@foo ... [echo] Hello Foo!!
antrun:run
だけで実行した場合は、default-cli
のほうが実行されるantrun:run@foo
のように、ゴール指定の後ろに@<id>
と続けることで、指定された<id>
の<execution>
が実行されるデフォルトのフェーズ
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
ゴールが実行されていることがわかる- ゴールは、デフォルトで紐づくフェーズを定義できるようになっている
- check ゴールの説明ページ を見ると、デフォルトフェーズが
verify
と書かれているのがわかる- もし
<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 は、次の順序で検索される
<relativePath>
が指定されている場合は、その場所の pom.xml を参照<relativePath>
が指定されていない場合は、1つ上の階層の pom.xml を参照- それも無ければ、リポジトリ(ローカル or リモート)を検索して参照
- ↑の例は親プロジェクトが子プロジェクトの1つ上だったため、特に場所の指定をしなくても親 POM が見つかっていた
親プロジェクトの場所を明示する
- しかし、↓のようなフォルダ構成の場合は、
<relativePath>
によるパスの指定が必要になるフォルダ構成|-parent/ | `-pom.xml `-child/ `-pom.xml
parent
とchild
が横並びになっており、子プロジェクトの1つ上に親プロジェクトの pom.xml が存在しない状態になっている- この状態で子プロジェクトで Maven を実行すると、次のようなエラーになる
実行結果(子プロジェクト)> mvn antrun:run ... [FATAL] Non-resolvable parent POM for example:child:1.0.0: Failure to find example:parent:pom:1.0.0 in https://repo.maven.apache.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced and 'parent.relativePath' points at wrong local POM @ line 6, column 11 @ ...
- これを解決するには、リポジトリに親プロジェクトの jar を登録するか、以下のように
<relativePath>
で親プロジェクトの場所を明示してあげる必要がある/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 でマージが行われているっぽい
MavenModelMerger
は mergeBuildBase_Resources() をオーバーライドしていて、target
(子POM)の<resources>
が空の場合のみ親POMの内容をマージするようにしている- つまり、子POMに
<resources>
が存在すればマージは行われず、子POMの内容だけが採用される(結果として上書きの動作になる)<resources>
以外は、このMavenModelMerger
の実装を見てみるしかない?マージ後の POM を確認する
- POM には親子関係があり、複数の POM を親に持つような場合は末端の POM を見ただけでは最終的な状態がわからない
- 特にビルドがうまく動作しないときは、 POM が期待通りにマージされているか確認したくなることがよくある
- そういうときは、
maven-help-plugin
の effective-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
ライフサイクルで実行されるゴールは以下になる
resources:resources
compiler:compile
resources:testResources
compiler:testCompile
surefire:test
jar:jar
install:install
deploy:deploy
- 各ゴールが何をしているのか、1つずつ見ていく
リソースを収集する
- 最初に実行されるのは、
maven-resources-plugin
の 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> </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/resources
とsrc/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 ソースをコンパイルする
- 次に実行されるゴールは、 maven-compiler-plugin の compile ゴールとなる
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-plugin
でsource
を指定するには、 source パラメータ を設定する
<configuration>
で指定することもできるが、maven.compiler.source
プロパティでも設定できるようになっている- ここでは、プロパティを使った設定を使用している(こっちの方が一般的?)
target
も同様で、maven.compiler.target
プロパティで設定できるようになっている実行結果> mvn compiler:compile ... [INFO] --- maven-compiler-plugin:3.1:compile (default-cli) @ hello --- ... [WARNING] File encoding has not been set, using platform encoding MS932, i.e. build is platform dependent! ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS ...
- コンパイルはできたが、なにやら警告が出ている
- 警告内容は、ソースファイルのエンコーディングが環境デフォルトに依存している、というもの
- ソースファイルのエンコーディングの指定は、 encoding パラメータ で行う
- 説明に書いているように、デフォルトでは
${project.build.sourceEncoding}
を使用するようになっている- しかし、この値は Super POM でも設定されていないので、結果的に環境デフォルト(Windows なら MS932 など)となっている
- ということで、エンコーディングも設定してみる
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 上で参照しようとしても、値を確認することはできないcompileSourceRoots
は MavenProject クラスのフィールドとして宣言されている- このフィールドは、 DefaultProjectBuilder によって値が設定されている
- この実装から、
project.build.sourceDirectory
がcompileSourceRoots
に設定されていることが分かる- そして、
project.build.sourceDirectory
には、 Super POM によって${project.basedir}/src/main/java
が設定されている- この仕組みを見ると、ソースフォルダを複数設定できないことが予想できる
- 複数のソースフォルダを設定したい場合は、 build-helper-maven-plugin というプラグインを導入する必要がある
- 整理すると、
compiler:compile
では次のことが行われる
${project.basedir}/src/main/java
の下にある Java ソースコードをコンパイルして、${project.basedir}/target/classes
に出力する- javac の
-source
,-target
オプションは、maven.compiler.source
,maven.compiler.target
プロパティで指定できる- エンコーディングの指定は、
project.build.sourceEncoding
プロパティで指定できる- ソースフォルダは
<project><build><sourceDirectory>
で指定できる
- 複数のフォルダを指定したい場合は、
build-helper-maven-plugin
が必要- 出力先フォルダは、
<project><build><outputDirectory>
テスト用のリソース収集とコンパイル
- 次に実行されるのは、テスト用のリソースを収集する resources:testResources と、
テスト用のソースコードをコンパイルする compiler:testCompile になる- それぞれの基本的な動作は
resources:resources
,compiler:compile
と同じなので、ざっくりとだけフォルダ構成|-pom.xml `-src/test/ |-java/ | `-example/ | `-HelloTest.java | `-resources/ |-foo/ | `-bar.txt `-fizz.txt
src/test/java
の下にテスト用のソースコードを、
src/test/resources
の下にテスト用のリソースファイルを配置する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>
で指定できるテストを実行する
- 次は、コンパイルしたテストコードを実行するため、 maven-surefire-plugin の test ゴール が実行される。
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 に固める
- コンパイル結果を jar に固める処理は、 maven-jar-plugin の 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.txtpackageフェーズを実行> 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.txt
とHello.class
は、それぞれresources:resources
,compiler:compile
によってtarget/classes
の下に出力されたものが梱包されている
- どのフォルダの内容が jar に梱包されるかは、 jar ゴールの classesDirectory パラメータによって設定されている
- このパラメータのデフォルトは、
${project.build.outputDirectory}
となっている(${project.basedir}/target/classes
)META-INF
の下に pom.xml が出力されているが、その内容はこのプロジェクトの pom.xml と同じものが入っているMANIFEST.MF
とpom.properties
は、それぞれ次のような内容になっているMANIFEST.MFManifest-Version: 1.0 Archiver-Version: Plexus Archiver Created-By: Apache Maven 3.6.3 Built-By: xxxxx Build-Jdk: 11.0.6pom.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.MF
やMETA-INF
以下に追加される情報を設定できるローカルリポジトリにインストールする
install
フェーズに紐付けられている maven-install-plugin の 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> ... </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.javapackage example; public class Foo { public static void main(String... args) { new Hello().hello(); } }
Hello
クラスを使った実装を書いているプロジェクトをコンパイル> mvn compile ... [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ foo --- ... [INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ foo --- ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ ...
- コンパイルが通った(
Hello
クラスが解決できている)
- 整理すると、
install
ゴールは次のように動作する- おそらく
package
フェーズで生成された成果物(jar ファイルなど)を、ローカルリポジトリにインストールする- インストールされたアーティファクトは、ローカルの他のプロジェクトから依存対象として参照できるようになる
デプロイする
- 最後に実行されるのが
deploy
フェーズとなるdeploy
フェーズでは、 maven-deploy-plugin の deploy ゴール が実行されるdeploy
ゴールは、プロジェクトの成果物をリモートのリポジトリにデプロイする環境構築とかが面倒な割に実際に使う機会は少ない気がするので検証は省略Web プロジェクトのビルド
- packaging を
war
にした場合に、プロジェクトがどのようにビルドされるかを見る- ただ、
default
フェーズで実行されるプラグインは、jar
の場合とほとんど違いはないjar
と異なるのは、package
フェーズで実行されるゴールが war プラグインの war ゴール ということだけフォルダ構成|-pom.xml `-src/main/ |-java/ | `-example/webapp/ | `-HelloServlet.java `-webapp/ `-WEB-INF/ `-hello.jsp
src/main/webapp
というフォルダを作成し、その下にwar
に含めるWEB-INF
などのフォルダを作成している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.javapackage 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>
- exec-maven-plugin というプラグインを追加している
実行> mvn compile exec:java ... [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ hello --- Hello World! args=[one, two, three] ...
Hello.java
が実行されたexec-maven-plugin
の java ゴール を使用すると、プロジェクトのビルド結果をクラスパスに入れて Java プログラムを実行できる
exec-maven-plugin
には任意のコマンドを実行できる exec ゴール も用意されているが、ここでは割愛- mainClass パラメータで Main クラスを指定することで、 Java プログラムを実行できる
- arguments パラメータで、コマンドライン引数を渡すことができる
commandlineArgs
というパラメータでも引数を渡すことができるが、こちらは後述するコマンドラインから実行する場合に利用するためにある(たぶん)コマンドラインから実行する
- 最初の例は、あらかじめ pom.xml に実行の構成をすべて記述しておく必要がある
- 場合によっては、コマンドラインで引数を色々変更しながら実行したくなることもあるかもしれない
- その場合は、システムプロパティを介してゴールのパラメータを指定する方法をとるとやりやすい
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
は特殊なので除外)
○
が付いているのは、ゴールが該当するスコープの依存関係を参照・使用することを表している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-plugin の tree ゴール を実行すると、この推移的な依存関係を含めたすべての依存関係をツリー構造のグラフで確認できる
実行結果> mvn dependency:tree ... [INFO] +- org.springframework:spring-web:jar:5.2.5.RELEASE:compile [INFO] | +- org.springframework:spring-beans:jar:5.2.5.RELEASE:compile [INFO] | \- org.springframework:spring-core:jar:5.2.5.RELEASE:compile [INFO] | \- org.springframework:spring-jcl:jar:5.2.5.RELEASE:compile [INFO] \- org.springframework:spring-jdbc:jar:5.2.5.RELEASE:compile [INFO] \- org.springframework:spring-tx:jar:5.2.5.RELEASE:compile ...
spring-web
が、さらにspring-beans
,spring-core
に依存し、さらにspring-core
がspring-jcl
に依存していることが分かる- そして、
spring-jdbc
はspring-tx
に依存していることがわかる
- ちなみに、
spring-jdbc
はspring-beans
,spring-core
にも依存しているが、spring-web
側と重複するため表示は省略されている- デフォルトでは全てのスコープの依存関係が出力されるが、 scope パラメータ で絞ることもできる
依存関係のjarを集める
- プロジェクトが依存する jar ファイルの実物を手元にすべてかき集めたい、となることが稀によくある
- これは、
maven-dependency-plugin
の copy-dependencies ゴール で実現できる- ↑の pom.xml と同じ設定で実行してみる
実行結果> mvn dependency:copy-dependencies ... [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ ... > dir /b target\dependency spring-beans-5.2.5.RELEASE.jar spring-core-5.2.5.RELEASE.jar spring-jcl-5.2.5.RELEASE.jar spring-jdbc-5.2.5.RELEASE.jar spring-tx-5.2.5.RELEASE.jar spring-web-5.2.5.RELEASE.jar
target/dependency
フォルダの下に、依存する jar がすべて出力されている- デフォルトでは全てのスコープの jar が対象になるが、 includeScope パラメータ などで絞り込むことができる
プロファイル
- 基本的にプロジェクトのビルドは、どの環境でも同じ設定で同じ結果が出力されるようにしておくのがいい
- ある環境ではビルドは成功するけど、別の環境ではエラーになる、なんてことになると色々面倒
- しかし、実際に開発をしていると、環境に依存したビルド設定が必要になることが稀によくあるらしい(しらんけど)
- ビルド環境によって pom.xml の内容を切り替えたい(特定のプラグインを有効にしたり、設定値を変更したり)場合に、プロファイル(Profile)という仕組みが利用できる
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>
foo
とbar
という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 のバージョンと比較される
11
と指定してる場合、前方一致なので11.0.6
にもマッチする- さらに
(,11]
のような範囲指定の記述も可能(この場合は、 11 以下のバージョンにマッチする)- 範囲指定の書き方については Apache Maven Enforcer Built-In Rules – Version Range Specification を参照
- なお、比較対象となる 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
で取得できる値が使用されている- 大文字・小文字の区別はないっぽい
- 名前以外にも、次の条件を指定できる
<family>
- OS の種類
- 指定できる値の一覧は Apache Maven Enforcer Built-In Rules – Require OS Version を参照
- ちなみに、 Linux の場合は
unix
を指定する<arch>
- CPU のアーキテクチャ
x86
とかamd64
を指定する<version>
- OS のバージョン
- これらの条件を複数指定した場合は、全て AND 条件で結合される
- 今の環境がどういう値になるのかは、 maven-enforcer-plugin の display-info ゴール を実行することで確認できる
実行結果> mvn enforcer:display-info ... [INFO] Maven Version: 3.6.3 [INFO] JDK Version: 11.0.6 normalized as: 11.0.6 [INFO] OS Info: Arch: amd64 Family: windows Name: windows 10 Version: 10.0ファイルの有無を条件にする
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)
- maven-help-plugin の all-profiles を実行すると、使用できるプロファイルを確認できる
アクティブなプロファイルを確認する
実行結果> mvn -P foo help:active-profiles antrun:run ... Active Profiles for Project 'example:hello:jar:1.0.0': The following profiles are active: - foo (source: example:hello:1.0.0) ... [echo] message = foo profile!!
- active-profiles を使用すると、実行時にアクティブ(有効)になっているプロファイルを確認できる
- デバッグのときとかに便利かも
いつプロファイルを使うべきか
- プロファイルを使うと、プロファイルごとに異なるビルド結果を作り出すことができるようになる
- このため、プロファイルを多様しすぎると、開発者によって異なるビルド結果が得られてしまうことになりかねない
- 「開発用途では
develop
プロファイルを有効にしないといけない」みたいなルールがあって、その指定を忘れていたなど- ビルド結果が変わるのは、混乱の原因になりかねない
<os>
などの条件設定が可能なことから想像できるように、プロファイルの本来の目的は、ビルドを行う環境の差異を吸収することにあると思う
- したがって、それ以外の用途で用いるのは極力避けたほうが良い気がする(私見)
- 例えば、開発用・検証環境用・本番環境用に使用する設定ファイルを切り替えるような方法では、プロファイルは使うべきではないのかもしれない
- アプリの設定を環境ごとに切り替える手段は、最近は環境変数を使用するのがベストプラクティスとされている気がする
- 参考:III. 設定 | The Twelve-Factor App (日本語訳)
プラグインを自作する
- プラグインは自作できる
org.apache.maven.plugins
やcom.codehaus.mojo
にあるプラグインで目的が達成できない場合は、自分でプラグインを作ることになるHello World
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.javapackage 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.javapackage 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.javapackage example; ... @Mojo(name="hello") public class HelloMojo extends AbstractMojo { @Parameter(property="hello.message") private String message; public void execute() throws MojoExecutionException { getLog().info("message = " + message); } }
@Parameter
のproperty
に、システムプロパティで指定するときの名前を設定する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.javapackage 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.javapackage 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.javapackage 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} ...
int
やlong
,float
,double
,boolean
などのプリミティブ型は、普通に使用可能
- ラッパークラスも可能
java.util.Date
は、次のいずれかの書式で指定する
yyyy-MM-dd HH:mm:ss.S a
(例:2005-10-06 2:22:55.1 PM
)yyyy-MM-dd HH:mm:ssa
(例:2005-10-06 2:22:55PM
)- ただし、完全に一致していなくても、そこそこ柔軟に解析してくれる(AM, PM は省略できるけど、時刻は省略できないっぽい)
java.io.File
は、値をパスとして扱うjava.net.URL
は、値を URL として扱う- enum 型の場合は、列挙された定数と同じ値を指定することで設定可能
List
の場合は、パラメータ名と同じ要素を書いた中に任意の名前の要素を列挙すれば、それがList
の要素として処理されるっぽい
- 普通は
<listValues><listValue>...
のように記述するのがわかりやすいと思うMap
の場合は、入れ子要素の名前がキーに、要素の値がバリューになる
- 検証は省略したけど、
java.util.Properties
も同じように使用できるデフォルトのフェーズを紐付ける
HelloMojo.javapackage example; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; @Mojo(name="hello", defaultPhase=LifecyclePhase.VALIDATE) public class HelloMojo extends AbstractMojo { public void execute() throws MojoExecutionException { getLog().info("Hello Mojo!!"); } }
@Mojo
のdefaultPhase
で、デフォルトのフェーズを指定できる- フェーズの指定には、
LifecyclePhase
列挙型を使用する- ここでは
validate
フェーズに紐づけている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-plugin の clean ゴール を実行すると、プロジェクトの生成物をすべて削除できる
- ここで指定しているのは
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>
でファイルを絞ることもできるっぽい(試してない)参考兼公式ドキュメント目次
どこになんの情報があるのか分かりにくすぎるので整理してみる
- POM
- プラグイン
- ビルドライフサイクル
- プロファイル
- 依存関係の解決
- フォルダ構成
- Javadoc
Maven 1.0 が 2004 年、Maven 2.0 が 2005 年、Maven 3.0 が 2010 年(Maven – Maven Releases History) ↩
%USERPROFILE%
はユーザのホームを指す Windows の環境変数(Linux とかなら$HOME
のこと) ↩「
<modelVersion>
も Super POM にあるやん」って思うけど、これは残念ながら継承されないもよう(消すとエラーになる) ↩厳密には
compiler::::compile
のような指定の場合もプレフィックス指定と判定されるけど、ここはわかりやすさを優先してコロンの数としている(詳しくは MojoDescriptorCreator の実装を参照) ↩
<release>
はリリースバージョン内での最新なのに対して、<latest>
はスナップショットも含めて最新のバージョンを指している ↩厳密には
settings.xml
の<pluginGroups>
で検索対象のgroupId
を追加できる(参考:Introduction to Plugin Prefix Resolution) ↩In Maven 2.0.5 and above, multiple goals bound to a phase are executed in the same order as they are declared in the POM, ↩