CLOVER🍀

That was when it all began.

Apache Mavenで依存関係の管理

前回は、Apache Mavenのインストールとプロジェクト作成、起動までやりました。今回は、Mavenを使った依存関係の管理をやりたいと思います。

まず、不要な雛型クラスを削除

$ rm src/main/java/myapp/hello/App.java 
$ rm src/test/java/myapp/hello/AppTest.java 

今回は…そうですね、Jakarta Commons Langでも使ってみましょうか。新しいJavaクラスを作成します。

$ emacs src/main/java/myapp/hello/HelloCommons.java &

とりあえず、以下のような簡単なクラスを作成してみました。

package myapp.hello;

import org.apache.commons.lang.StringUtils;

public class HelloCommons {
    public static void main(String[] args) {
        String message = "Hello %replace%!";
        String target;

        if (args.length > 0) {
            target = args[0];
        } else {
            target = "Commons";
        }

        System.out.println(StringUtils.replace(message, "%replace%", target));
    }
}

では、コンパイルしてみましょう。コンパイルは、「mvn compile」で実行します。

$ mvn compile
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building myapp-hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ myapp-hello ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /xxxxx/myapp-hello/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ myapp-hello ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /xxxxx/myapp-hello/target/classes
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /xxxxx/myapp-hello/src/main/java/myapp/hello/HelloCommons.java:[3,30] パッケージ org.apache.commons.lang は存在しません。
[ERROR] /xxxxx/myapp-hello/src/main/java/myapp/hello/HelloCommons.java:[16,27] シンボルを見つけられません。
シンボル: 変数 StringUtils
場所    : myapp.hello.HelloCommons の クラス
[INFO] 2 errors 
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.772s
[INFO] Finished at: Thu May 19 21:20:37 JST 2011
[INFO] Final Memory: 6M/105M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.2:compile (default-compile) on project myapp-hello: Compilation failure: Compilation failure:
[ERROR] /xxxxx/myapp-hello/src/main/java/myapp/hello/HelloCommons.java:[3,30] パッケージ org.apache.commons.lang は存在しません。
[ERROR] /xxxxx/myapp-hello/src/main/java/myapp/hello/HelloCommons.java:[16,27] シンボルを見つけられません。
[ERROR] シンボル: 変数 StringUtils
[ERROR] 場所    : myapp.hello.HelloCommons の クラス
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException

当然といえば当然ですが、コンパイルに失敗します。

これは、プロジェクトの依存ライブラリを定義していないことが原因です。では、依存関係を定義しましょう。Mavenのセントラルリポジトリから、Jakarta Commons Langを探します。

Maven Central
http://search.maven.org/#search

上記サイトで、Commons Langを探して依存関係を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>myapp.hello</groupId>
  <artifactId>myapp-hello</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myapp-hello</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- Commmons Lang の依存性定義 -->
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
      <scope>compile</scope>  <!-- これは指定しなくても同じ -->
   </dependency>
    <!-- Commmons Lang の依存性定義 -->
  </dependencies>
</project>

2つ目のdependencyがCommons Langとの依存関係の定義です。なお、ここではscopeを明示的に指定しました。それでは、気を取り直してコンパイルしてみましょう。

$ mvn compile
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building myapp-hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.pom
Downloaded: http://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.pom (18 KB at 11.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/commons/commons-parent/17/commons-parent-17.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/commons/commons-parent/17/commons-parent-17.pom (31 KB at 52.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.jar
Downloaded: http://repo1.maven.org/maven2/commons-lang/commons-lang/2.6/commons-lang-2.6.jar (278 KB at 145.9 KB/sec)
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ myapp-hello ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /xxxxx/myapp-hello/src/main/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ myapp-hello ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /xxxxx/myapp-hello/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.934s
[INFO] Finished at: Thu May 19 21:32:02 JST 2011
[INFO] Final Memory: 6M/74M
[INFO] ------------------------------------------------------------------------

今度は成功しました。例によって処理途中で、Commons Langをダウンロードしてきています。なお、今回はCommons Lang単体で終了していますが、依存関係のあるライブラリがさらに別のライブラリに依存していた場合、必要であればそのライブラリもダウンロードしてきます。これを推移的依存関係の自動解決、というらしいです。

ところで、先ほど依存関係の定義の際にscopeを書きましたね。これの意味をまとめておきましょう。

スコープ名 意味
compile コンパイル時に必要な依存関係。scope省略時は、これがデフォルト。コンパイル時に必要なのだから、当然テスト、実行時にも必要
provided コンパイル時には必要だけど、実行時には実行環境より提供されるような依存関係。servlet-api.jarなど
runtime コンパイル時には不要だけど、実行時には必要な依存関係。JDBCドライバなど
test アプリケーション本体には不要だけど、テストには必要な依存関係
system providedとほぼ同じですが、JARのパスを明示的に指定する必要があります

んじゃ、実行してみましょうか。まずはパッケージングします。

$ mvn package

ちなみに、Mavenでは成果物のことをアーティファクトと呼ぶらしいです。単体のアプリケーションやライブラリだったらJARファイルが、WebアプリケーションならWARファイルが…などといった感じらしいです。まあ、それ以外のテストとかの工程の結果とかも含むっぽいですけれど。

では、Javaコマンドで実行…といきたいところですが、いきなり実行するようなフェーズはないのでexecプラグインを使用して実行します。

$ mvn exec:java -Dexec.mainClass=myapp.hello.HelloCommons -Dexec.arguments="World" -Dexec.classpathScope=runtime
[INFO] Scanning for projects...
Downloading: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/maven-metadata.xml
Downloaded: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/maven-metadata.xml (549 B at 0.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/1.2/exec-maven-plugin-1.2.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/1.2/exec-maven-plugin-1.2.pom (7 KB at 12.4 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/mojo/mojo-parent/24/mojo-parent-24.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/mojo/mojo-parent/24/mojo-parent-24.pom (23 KB at 40.8 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/1.2/exec-maven-plugin-1.2.jar
Downloaded: http://repo1.maven.org/maven2/org/codehaus/mojo/exec-maven-plugin/1.2/exec-maven-plugin-1.2.jar (36 KB at 39.7 KB/sec)
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building myapp-hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2:java (default-cli) @ myapp-hello >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2:java (default-cli) @ myapp-hello <<<
[INFO] 
[INFO] --- exec-maven-plugin:1.2:java (default-cli) @ myapp-hello ---
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-plugin-api/2.0/maven-plugin-api-2.0.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/maven-plugin-api/2.0/maven-plugin-api-2.0.pom (601 B at 1.3 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven/2.0/maven-2.0.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/maven/2.0/maven-2.0.pom (9 KB at 17.0 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.0.1/commons-exec-1.0.1.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.0.1/commons-exec-1.0.1.pom (8 KB at 15.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/commons/commons-parent/11/commons-parent-11.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/commons/commons-parent/11/commons-parent-11.pom (25 KB at 49.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.0.1/commons-exec-1.0.1.jar
Downloaded: http://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.0.1/commons-exec-1.0.1.jar (49 KB at 66.0 KB/sec)
Hello World!  <-- よく見ると、実行結果が!!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 6.970s
[INFO] Finished at: Thu May 19 21:45:02 JST 2011
[INFO] Final Memory: 6M/105M
[INFO] ------------------------------------------------------------------------

だいぶ壮大ですが、一応実行できました。初回なので、execプラグインのダウンロード処理に結果が埋もれてしまっていますけどね…。

引数多いわー!って場合は、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>myapp.hello</groupId>
  <artifactId>myapp-hello</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myapp-hello</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- Commmons Lang の依存性定義 -->
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
      <scope>compile</scope>  <!-- これは指定しなくても同じ -->
   </dependency>
    <!-- Commmons Lang の依存性定義 -->
  </dependencies>
  <!-- ここから追加 -->
  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.2</version>
        <executions>
          <execution>
            <goals>
              <goal>java</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <mainClass>myapp.hello.HelloCommons</mainClass>
          <classpathScope>runtime</classpathScope>
          <arguments>
            <argument>World</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

今度は、以下のコマンドだけで実行できるようになります。

$ mvn exec:java
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building myapp-hello 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> exec-maven-plugin:1.2:java (default-cli) @ myapp-hello >>>
[INFO] 
[INFO] <<< exec-maven-plugin:1.2:java (default-cli) @ myapp-hello <<<
[INFO] 
[INFO] --- exec-maven-plugin:1.2:java (default-cli) @ myapp-hello ---
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.047s
[INFO] Finished at: Thu May 19 23:47:32 JST 2011
[INFO] Final Memory: 4M/105M
[INFO] ------------------------------------------------------------------------

ところで、依存関係を書いたpom.xmlですが、これがプロジェクトの設定ファイル(Project Object Model)になります。これにプロジェクトの内容が記述されることになり、ライブラリの依存関係もそれに含まれます。適切な依存関係を書いていれば、自動的にライブラリをダウンロードしてきてくれます。

また、Antの時と違ってコンパイルなどをするだけなら、ほとんど設定ファイルであるpom.xmlには手を入れることがありません。Antでやる場合は、ソースコードがどこそこにあって、javacを実行して…なんて書くもんですが、Mavenの場合はそういうのがあまり登場しません。

これは、Mavenが決まりきったことは予めMaven側で定義しているためです。よって、利用者側はプロジェクト個別のカスタマイズ部のみを書く、というスタンスを取っています。だからMaven標準のディレクトリ構成に則っていれば、コンパイルは何も設定しなくてもできるというわけですね。このことから、Antは全てを記載していくため加算的アプローチ、Mavenは差分的アプローチであると言われています。

まあ、楽と言えば楽なんですけれど。ただ、Mavenの真価を引き出すには単なるビルドツールとして使っていてはダメなんですよね、きっと。