エンジニア的なネタを毎週書くブログ

東京でWebサービスの開発をしています 【英語版やってみました】http://taichiw-e.hatenablog.com/

Maven vs Java9のModule(Project Jigsaw) : 依存はどっちが勝つの? (追記:あんまりMaven関係なかった)

無事Moduleを使ったビルドがMavenでできたところで、本当にやりたかった検証へ。

Mavenで複数プロジェクトが同一プロジェクトに依存していると、「どれか一つ」が使われることになります。
全て同じバージョンなら問題にならないのですが、それぞれ異なるバージョンのプロジェクトを使用していると、結果、意図せぬ不具合を引き起こすことになります。

もしかしてmoduleはこの問題も解決したのでは? と期待したのですが… 結果、ダメそうでした!

検証内容

こういう依存関係のものを作ります。
f:id:taichiw:20171002232722p:plain

MultiVersion.java

package multi.version;

public class MultiVersion {
	public static String version () {
		return "1";
	}
}

multi.version/pom.xml(一部)

<groupId>multiversion</groupId>
<artifactId>multi.version</artifactId>
<version>1.0</version>

World.java

package org.astro;
import multi.version.MultiVersion;

public class World {
	public static String name() {
		return "world" + MultiVersion.version();
	}
}

org.astro/pom.xml (一部)

<dependencies>
  <dependency>
  <groupId>multiversion</groupId>
  <artifactId>multi.version</artifactId>
  <version>1.0</version>
  </dependency>
</dependencies>

Main.java

package com.greetings;

import org.astro.World;

public class Main {
	public static void main(String[]args){
		System.out.format("Greetings %s!%n", World.name());
	}
}

f:id:taichiw:20171002233028p:plain

ビルドして実行すると

$ java -jar com.greetings/target/com.greetings-1.0-SNAPSHOT-jar-with-dependencies.jar
Greetings world1!

ここまではOK.

さてここで、
multi.versionモジュールのversion2.0を作ります。
依存を追加するだけで、特にrequiresやimportはしません。

MultiVersion.java

package multi.version;

public class MultiVersion {
	public static String version () {
		return "2";
	}
}

multi.version/pom.xml

<groupId>multiversion</groupId>
<artifactId>multi.version</artifactId>
<version>2.0</version>

そして com.greetingsのpom.xmlにversion 2.0のmultiversionへのdependencyを追加します。

<dependencies>
  <dependency>
    <groupId>groupId</groupId>
    <artifactId>org.astro</artifactId>
    <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
    <groupId>multiversion</groupId>
    <artifactId>multi.version</artifactId>
    <version>2.0</version>
  </dependency>
</dependencies>

f:id:taichiw:20171002234426p:plain

GitHub - taichiw/moduleTry at 3.0_module_tukattemo_dame


これで実行すると…

$ java -jar com.greetings/target/com.greetings-1.0-SNAPSHOT-jar-with-dependencies.jar
Greetings world2!

2が返ってきてしまいました。
たとえModuleを使っても、Mavenビルド時に、「より外側」に書かれたプロジェクトが使われてしまうことに変わりはないようです。
これだと、Moduleは、Module提供者が意図とした環境での実行が行われるとは限らないことになります。


さらに、MultiVersionクラスのメソッド名を変えてみます。

MultiVersion.java

package multi.version;

public class MultiVersion {
	// changed method name version() -> version3()
	public static String version3 () {
		return "3";
	}
}


これでもビルドは通りますが…

$ java -jar com.greetings/target/com.greetings-1.0-SNAPSHOT-jar-with-dependencies.jar
Exception in thread "main" java.lang.NoSuchMethodError: multi.version.MultiVersion.version()Ljava/lang/String;
        at org.astro.World.name(World.java:6)
        at com.greetings.Main.main(Main.java:7)

実行時エラーになってしまいました。

残念。

追記 (2017/10/05)

Mavenが悪いみたいな書き方したけど、そもそも現行のModuleの仕様の問題っぽい気がする。

mavenで作られたjarファイルを1ディレクトリに集めて…

$ ls mlib
com.greetings-1.0-SNAPSHOT.jar  multi.version-1.0.jar  org.astro-1.0-SNAPSHOT.jar

javaコマンドでmoduleとして実行すると

$ java -p mlib -m com.greetings/com.greetings.Main
Greetings world1!

multi.versionを2.0に置き換えて実行すると

$ cp multiversion/target/multi.version-2.0.jar mlib/

$ mv mlib/multi.version-1.0.jar mlib/multi.version-1.0.jar_

$ ls mlib/
com.greetings-1.0-SNAPSHOT.jar  multi.version-1.0.jar_  multi.version-2.0.jar  org.astro-1.0-SNAPSHOT.jar

$ java -p mlib -m com.greetings/com.greetings.Main
Greetings world2!

2になっちゃう。


moduleをビルドするときには--module-versionコマンドでバージョン指定しながらモジュールが作れるので、特定のバージョンに対しての依存も貼れそうなんだけど…
ひとまず方法が見つからず。


追記
今日、JavaOneでセッションがあったようで… あとで資料 or tweet or なんか無いか探してみよう。
f:id:taichiw:20171003001021p:plain