はじめに
最近Javaのビルドツールの1つであるMavenについて学ぶ機会がありました。Javaのビルドには複数の方法が存在しますが、その中でも現在主流となっているMavenの利点を理解するために、本記事ではMaven登場以前に使われていた手動ビルドおよびAntと比較しながら、実際に手を動かして体験しました。
検証内容
今回は簡単なクラス、Hello.javaを用意し、手動、Ant、Mavenでそれぞれの手段で確認します。もっとMavenの利点等はあると思うのですが、今回はコンパイル・JAR化・ライブラリ導入時の3つの工程でそれぞれの方法を比較してみます。
1.コンパイル
コンパイルとはJavaファイルに記載した内容がコンピュータでも読み取れるように機械語に変更するプロセスのことを指します。
手動の場合
手動の場合、javacのコマンドを利用します。コンパイルしたいJavaファイルのパスと-dで出力先を指定することでそのファイル以下にclassファイルが作成されます。
javac -d bin src/com/example/Hello.java
-dで指定せずとも作成することはできますが、以下のように.javaと.classが混ざってしまいクラスファイルが増えると、管理が難しくなります。
javac src/com/example/Hello.java
Antの場合
AntではHello.java以外にもbuild.xmlを用意します。
build.xmlには手動でコンパイルしていたときにコマンドで入力していたJavaファイルのパスや出力先をあらかじめ記載しておきます。
<project name="HelloAnt" default="compile" basedir=".">
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<target name="compile">
<mkdir dir="${build.dir}"/>
<javac srcdir="${src.dir}" destdir="${build.dir}"/>
</target>
</project>
記述後、以下のコマンドを実行するとBUILD SUCCESSFULと表示され、build.xmlで指定した出力先にclassファイルが作成されます。
ant compile
Mavenの場合
MavenではHello.java以外にもpom.xmlを用意します。
これまでは手動やAntでのコンパイル時にコンパイルさせたいJavaファイルのパスや出力先をあらかじめ記載していましたが、Mavenでは標準的なディレクトリ構成に従うことで、コンパイル対象や出力先を明示的に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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>hello-maven</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
記述後、以下のコマンドを実行するとBUILD SUCCESSと表示され、target以下にclassファイルが作成されます。手動やAntで実行した場合と異なり、コマンドやbuild.xmlで指定せずともデフォルトに従った構成で作成していれば、target以下に自動的にclassファイルが作成されます。
2.JAR化
JARファイルにすると、複数のクラスをまとめて圧縮し配布しやすい形にできます。
手動の場合
実行可能なJARファイルを作成するためには、mainメソッドを含むクラスを Main-Classとしてmanifest.txtに指定する必要があります。記述後、クラスパスや出力先を指定してJARファイルを作成します。
Main-Class: com.example.Hello
mkdir dist
jar cfm dist/hello.jar manifest.txt -C bin .
またコマンドでJARファイル名を任意に変更できます
jar cfm dist/hello2.jar manifest.txt -C bin .
Antの場合
Antではbuild.xmlに出力先やjar実行時のmainメソッドの情報を追記します。
<target name="jar">
<jar destfile="dist/hello.jar" basedir="build">
<manifest>
<attribute name="Main-Class" value="com.example.Hello"/>
</manifest>
</jar>
</target>
以下のコマンドを実行すると、BUILD SUCCESSFULと表示されbuild.xmlで指定したファイル階層にjarが作成されます。コンパイル時と同様、あらかじめbuild.xmlに記載しておくことで都度コマンド内で指定する必要がなくなります。
ant jar
Mavenの場合
Mavenではpom.xmlにJAR実行時のmainメソッドの情報を追記します。
<build>
<plugins>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<archive>
<manifest>
<mainClass>com.example.Hello</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
記述後、以下のコマンドを実行するとBUILD SUCCESSと表示されます。コンパイル時と同様、コマンドやbuild.xmlで指定せずともデフォルトに従った構成で作成していれば、Mavenが自動的にtarget以下にJARを生成してくれます。
mvn package
3.ライブラリ導入
効率的に開発を進めていくには、ライブラリの存在は欠かせません。
Hello.javaをApache Commons Langを利用した内容に修正してからコンパイルしそれぞれの方法でどのような違いがあるのかを比較します。
手動の場合
コンパイルする為に、試しに1.コンパイル時に入力したのと同じコマンドで実行します。
javac -d bin src/com/example/Hello.java
1.コンパイル時は、このコマンドによって指定したファイル以下にclassファイルが作成されました。今回は使用ライブラリが見つからず、コンパイルエラーが発生しました。
src\com\example\Hello.java:3: エラー: パッケージorg.apache.commons.lang3は存在しません
import org.apache.commons.lang3.StringUtils;
^
src\com\example\Hello.java:8: エラー: シンボルを見つけられません
String capitalized = StringUtils.capitalize(input); // → "Hello, build tools!"
^
シンボル: 変数 StringUtils
場所: クラス Hello
エラー2個
このエラーを解消するには、実際にライブラリをダウンロードして保存する必要があります。今回はlibフォルダーを作成し、その中にダウンロードしたjarを保存しました。この状態でクラスパスを指定し、以下のコマンドを実行するとclassファイルが作成されました。
javac -cp lib/commons-lang3-3.17.0.jar -d bin src/com/example/Hello.java
Antの場合
こちらも試しに1.コンパイル時に入力したのと同じコマンドで実行します。
ant compile
1.コンパイル時にはこの状態でも、コンパイルが成功しclassファイルが作成されました。文字化けして、詳細な内容を確認できませんが今回は手動のときと同様に、利用しているライブラリが見つからなことによるエラーが生じました。
[javac] C:\***\***\Desktop\qiita\project-ant\src\com\example\Hello.java:3: ▒G▒▒▒[: ▒p▒b▒P▒[▒Worg.apache.commons.lang3▒͑▒▒݂▒▒܂▒▒▒
[javac] import org.apache.commons.lang3.StringUtils;
[javac] ^
[javac] C:\***\***\Desktop\qiita\project-ant\src\com\example\Hello.java:8: ▒G▒▒▒[: ▒V▒▒▒{▒▒▒▒▒▒▒▒▒▒▒܂▒▒▒
[javac] String capitalized = StringUtils.capitalize(input); // ▒▒ "Hello, build tools!"
[javac] ^
[javac] ▒V▒▒▒{▒▒: ▒ϐ▒ StringUtils
[javac] ▒ꏊ: ▒N▒▒▒X Hello
[javac] ▒G▒▒▒[2▒▒
BUILD FAILED
そこで、クラスパスを指定するためにlibフォルダーを作成しその中にダウンロードしたjarを保存しました。そして、build.xmlにクラスパスを明記し同様のコマンドを実行します。
<project name="HelloAnt" default="compile" basedir=".">
<property name="src.dir" value="src"/>
<property name="build.dir" value="build"/>
<property name="lib.dir" value="lib"/>
<!-- ライブラリのクラスパス定義 -->
<path id="classpath">
<fileset dir="${lib.dir}">
<include name="*.jar"/>
</fileset>
</path>
<target name="compile">
<mkdir dir="${build.dir}"/>
<javac
srcdir="${src.dir}"
destdir="${build.dir}"
encoding="UTF-8"
classpathref="classpath"/>
</target>
</project>
ant compile
指定したクラスが見つからないことによってエラーが生じていましたが、今回はコンパイルが成功し、指定したファイル以下にclassファイルが作成されました。
Mavenの場合
こちらも試しに1.コンパイル時に入力したのと同じコマンドで実行します。
mvn compile
1.コンパイル時にはこの状態でも、コンパイルが成功しclassファイルが作成されました。文字化けして、詳細な内容を確認できませんが今回は手動のときと同様に、利用しているライブラリが見つからなことによるエラーが生じました。
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.247 s
[INFO] Finished at: ***************
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project hello-maven: Compilation failure: Compilation failure:
[ERROR] /C:/***/***/Desktop/qiita/project-maven/src/main/java/com/example/Hello.java:[3,32] ▒p▒b▒P▒[▒Worg.apache.commons.lang3▒͑▒▒݂▒▒܂▒▒▒
[ERROR] /C:/***/***/Desktop/qiita/project-maven/src/main/java/com/example/Hello.java:[8,30] ▒V▒▒▒{▒▒▒▒▒▒▒▒▒▒▒܂▒▒▒
[ERROR] ▒V▒▒▒{▒▒: ▒ϐ▒ StringUtils
[ERROR] ▒ꏊ: ▒N▒▒▒X com.example.Hello
[ERROR] -> [Help 1]
そこでpom.xmlに依存関係を追加するdependenciesタグを追加します。ここにはどのライブラリを利用するかのみを記載します。
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
この状態で、再度以下のコマンドを実行するとtarget以下にclassファイルが作成されました。手動やAntと異なり、JARをダウンロードせずともコンパイルすることができました。
mvn compile
ダウンロードしたリポジトリは.m2フォルダのリポジトリに保存されます。(コンパイル時にファイルが生成されたことを確認しました。)
感じたこと
今回は、コンパイル・JAR化・ライブラリ導入この3つで、手動・Ant・Mavenを比較してみました。手動・Antと比較してMavenの利便性を感じた点は2つあります。
ファイル構成を気にしなくてもよい
主に、コンパイル・JAR化をして感じたことです。
手動でコンパイルしたり、JAR化させた際に毎回どのクラスが対象なのか、出力先がどこなのかコマンドで指定する必要がありました。また今回は1つのJavaファイルのみでしたが、複数のファイルを一気にコンパイルする際は少し工夫も必要かと思います。コマンドで毎回選択していると、知らないうちに誤って意図しない場所にclassファイルが出力されてしまう可能性があります。Antではbuild.xmlで事前に指定することでコマンドを簡略化することができました。しかし自由に出力先などを設定できる分、運用なども複雑になっていくためチームでの開発や複雑な管理をしている場合だと、混乱しやすいなと感じました。
一方で、Mavenではpom.xmlに特に出力先等は記載せずに簡潔なコマンドを実行することで自動的にtargetフォルダ以下にclassファイルが作成されました。調べたところ、出力先を指定することも可能らしいですがMavenの利点として構成がすでに決まっていることも一つに挙げられるようです。手動やAntなどを利用して自由度を上げることでビルドの運用や管理を難しくさせるぐらいなら、あらかじめ指定されたフォルダで管理するMavenを採用し実装など本来注力すべき実装に時間を割いたほうがいいなと感じました。
負担なくいろいろなライブラリを利用できる
ライブラリの導入時に感じたことです。
手動やAntで試したときは、ライブラリを導入するのにサイトからダウンロードしていました。手動ではコンパイル時にコマンドでクラスパスを指定したり、Antではコマンドこそ簡単なもののbuild.xmlに記載する必要がありました。今回は一つのライブラリのみをインストールしたのでそこまで大変ではありませんでしたが、実装時に複数のライブラリを使用する場合は都度ダウンロードする必要があるので手間がかかる可能性があります。
一方でMavenの場合は、自分の手でダウンロードしなくとも自動的に依存関係を解決・取得してくれるのでひと手間省けます。また手動やAntとは異なり、クラスパスは指定せずともpom.xmlに利用するライブラリさえ記載していればコンパイルすることができました。ここからも実装に集中できる環境をMavenが作っているとも考えることができます。これが可能になったのも、Mavenが構成を定めているからとも言えます。
さいごに
今回はMavenの利便性を理解するために、手動やAntも利用して試してみました。コンパイル・JAR化・ライブラリの導入と3つの手段で比較しましたが、テストなど他にもMavenが優れている箇所は多くあります。今回、実際に手を動かしたことによってMavenの利便性を少し理解できただけでなく、手動やAntを使用することの大変さを学ぶことができました。また曖昧だったコンパイルやJARについても復習できたので一石二鳥でした。
記事が良かった・参考になったと思ったらいいね!を付けてくれると嬉しいです!