Java 8から、jdepsというクラスの依存関係を解析するツールが増えたそうです。
Java class dependency analyzer.
http://download.java.net/jdk8/docs/technotes/tools/unix/jdeps.html
http://download.java.net/jdk8/docs/technotes/tools/windows/jdeps.html
ちょっと興味があったので、試してみることにしました。
使ってみる
まずは、スケープゴート的なソースを用意してみます。
PrintLoop.java
import java.util.stream.IntStream; public class PrintLoop { public static void main(String... args) { IntStream .rangeClosed(1, 10) .forEach(System.out::println); } }
まあ、初Java 8コード!
とりあえず、コンパイルします。
$ javac PrintLoop.java
この.classファイルを、jdepsコマンドに食わせてみます。
$ jdeps PrintLoop.class PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar <unnamed> (PrintLoop.class) -> java.io -> java.lang -> java.lang.invoke -> java.util.function -> java.util.stream
と、依存関係が表示されました。importで書いたもの以上に、使用されているものが表示されている感じですね。
ヘルプを見てみる
先に見ようよ、という気がしますが。
$ jdeps -help 使用方法: jdeps <options> <classes...> <classes>には、.classファイルのパス名、ディレクトリ、JARファイルまたは完全修飾 クラス名を指定できます。使用できるオプションは次のとおりです: -dotoutput <dir> DOTファイル出力の宛先ディレクトリ -s -summary 依存性の要約のみ出力します -v -verbose クラス・レベルの依存性をすべて出力します -verbose:package パッケージ・レベルの依存性を出力します (同じアーカイブ内の依存性を除く) -verbose:class クラス・レベルの依存性を出力します (同じアーカイブ内の依存性を除く) -cp <path> -classpath <path> クラス・ファイルを検索する場所を指定します -p <pkgname> -package <pkgname> 指定のパッケージ内の依存性を検出します (複数回指定可能) -e <regex> -regex <regex> パターンに一致するパッケージ内の依存性を検出します (-pと-eは排他的) -include <regex> パターンに一致するクラスに分析を制限します このオプションを指定すると、分析対象クラスの リストがフィルタ処理されます。パターンを依存性に 適用する-pおよび-eと一緒に使用できます -P -profile プロファイル、またはパッケージを含むファイルを表示します -apionly 分析をAPI、つまり、パブリック・クラスの パブリック・メンバーおよび保護されたメンバーの 署名における依存性(フィールド・タイプ、メソッド・ パラメータ・タイプ、戻されたタイプ、チェックされた 例外タイプなど)に制限します -R -recursive すべての依存性を反復的に走査します -jdkinternals Finds class-level dependences on JDK internal APIs. By default, it analyzes all classes on -classpath and input files unless -include option is specified. This option cannot be used with -p, -e and -s options. WARNING: JDK internal APIs may not be accessible in the next release. -version バージョン情報
もう少し踏み込んでみましょう。
依存するクラスまで、詳細に表示してみる
オプション、「-verbose」または「-verbose:class」を付けて実行します。
「-verbose」
$ jdeps -verbose PrintLoop.class PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar PrintLoop -> java.io.PrintStream PrintLoop -> java.lang.Class PrintLoop -> java.lang.Object PrintLoop -> java.lang.String PrintLoop -> java.lang.System PrintLoop -> java.lang.invoke.CallSite PrintLoop -> java.lang.invoke.LambdaMetafactory PrintLoop -> java.lang.invoke.MethodHandle PrintLoop -> java.lang.invoke.MethodHandles PrintLoop -> java.lang.invoke.MethodHandles$Lookup PrintLoop -> java.lang.invoke.MethodType PrintLoop -> java.util.function.IntConsumer PrintLoop -> java.util.stream.IntStream
「-verbose」は、「-v」でもOKだそうな。
$ jdeps -v PrintLoop.class
「-verbose:class」
$ jdeps -verbose:class PrintLoop.class PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar PrintLoop (PrintLoop.class) -> java.io.PrintStream -> java.lang.Class -> java.lang.Object -> java.lang.String -> java.lang.System -> java.lang.invoke.CallSite -> java.lang.invoke.LambdaMetafactory -> java.lang.invoke.MethodHandle -> java.lang.invoke.MethodHandles -> java.lang.invoke.MethodHandles$Lookup -> java.lang.invoke.MethodType -> java.util.function.IntConsumer -> java.util.stream.IntStream
なお、「-verbose:package」というオプションもあるのですが、これがデフォルトみたいですね。
$ jdeps -verbose:package PrintLoop.class PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar <unnamed> (PrintLoop.class) -> java.io -> java.lang -> java.lang.invoke -> java.util.function -> java.util.stream
Profileを表示する
「-profile」で。
$ jdeps -profile PrintLoop.class PrintLoop.class -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar (compact1) <unnamed> (PrintLoop.class) -> java.io compact1 -> java.lang compact1 -> java.lang.invoke compact1 -> java.util.function compact1 -> java.util.stream compact1
JARファイルに対して、実行してみる
jdepsコマンドは、JARファイルに対しても実行できるようです。
ここはひとつ、Commons Langにスケープゴートになっていただきましょう。
$ jdeps commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3 (commons-lang3-3.3.1.jar) -> java.io -> java.lang -> java.lang.annotation -> java.lang.reflect -> java.nio.charset -> java.text -> java.util -> java.util.concurrent -> java.util.regex org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar) -> java.io -> java.lang -> java.lang.reflect -> java.util org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar) -> java.lang -> java.util -> java.util.concurrent -> java.util.concurrent.atomic org.apache.commons.lang3.event (commons-lang3-3.3.1.jar) 〜以下、省略〜
依存しているパッケージを指定して絞り込む
「-p」または「-package」オプションを指定することで、指定のパッケージに依存したパッケージなどを抽出することができます。
$ jdeps -package java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3 (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.event (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.exception (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.reflect (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.text (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.time (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.tuple (commons-lang3-3.3.1.jar) -> java.util
「-package」に続けて、複数回パッケージを指定可能とのこと。
これは、以下でもOKです。
$ jdeps -p java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar
「-v」と合わせると、それなりに大変なことになります。
$ jdeps -p java.util -v commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3.AnnotationUtils -> java.util.Arrays org.apache.commons.lang3.AnnotationUtils$1 -> java.util.Iterator org.apache.commons.lang3.AnnotationUtils$1 -> java.util.List org.apache.commons.lang3.ArrayUtils -> java.util.Arrays org.apache.commons.lang3.ArrayUtils -> java.util.BitSet org.apache.commons.lang3.ArrayUtils -> java.util.HashMap org.apache.commons.lang3.ArrayUtils -> java.util.Iterator org.apache.commons.lang3.ArrayUtils -> java.util.Map org.apache.commons.lang3.ArrayUtils -> java.util.Map$Entry org.apache.commons.lang3.ArrayUtils -> java.util.Set org.apache.commons.lang3.CharRange -> java.util.Iterator org.apache.commons.lang3.CharRange$CharacterIterator -> java.util.Iterator org.apache.commons.lang3.CharRange$CharacterIterator -> java.util.NoSuchElementException org.apache.commons.lang3.CharSet -> java.util.Collections org.apache.commons.lang3.CharSet -> java.util.HashMap org.apache.commons.lang3.CharSet -> java.util.HashSet org.apache.commons.lang3.CharSet -> java.util.Iterator org.apache.commons.lang3.CharSet -> java.util.Map org.apache.commons.lang3.CharSet -> java.util.Set org.apache.commons.lang3.ClassUtils -> java.util.ArrayList 〜以下、省略〜
指定のパッケージ内の依存関係を解析する
「-include」オプションを使用するようです。引数には、正規表現が使用できます。
$ jdeps -include 'org\.apache\.commons\.lang3\.text.*' commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> 見つかりません commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3.text (commons-lang3-3.3.1.jar) -> java.io -> java.lang -> java.text -> java.util -> org.apache.commons.lang3 見つかりません -> org.apache.commons.lang3.builder 見つかりません org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar) -> java.io -> java.lang -> java.util -> org.apache.commons.lang3 見つかりません
まあ、通常わざわざ「.」をエスケープしなくてもよいと思いますが。
ところで、指定には癖があるようで、以下は何も引っかかりません。
$ jdeps -include 'org\.apache\.commons\.lang3' commons-lang3-3.3.1/commons-lang3-3.3.1.jar
何かしら、全体でマッチするように指定しなさいということなのでしょうね。
$ jdeps -include 'org\.apache\.commons\.lang3.*' commons-lang3-3.3.1/commons-lang3-3.3.1.jar
依存先のパッケージを指定する
「-e」または「-regex」オプションを指定することで、依存先のパッケージを正規表現で指定することができます。
$ jdeps -e java.util.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3 (commons-lang3-3.3.1.jar) -> java.util -> java.util.concurrent -> java.util.regex org.apache.commons.lang3.builder (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.concurrent (commons-lang3-3.3.1.jar) -> java.util -> java.util.concurrent -> java.util.concurrent.atomic org.apache.commons.lang3.event (commons-lang3-3.3.1.jar) -> java.util -> java.util.concurrent org.apache.commons.lang3.exception (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.reflect (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.text (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.time (commons-lang3-3.3.1.jar) -> java.util -> java.util.concurrent -> java.util.regex org.apache.commons.lang3.tuple (commons-lang3-3.3.1.jar) -> java.util
以下でもOK。
$ jdeps -regex java.util.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar
これも、こういう指定だと何も起こりません…。
$ jdeps -e java.util commons-lang3-3.3.1/commons-lang3-3.3.1.jar
「-e」と「-regex」は排他的で、両方指定すると、後で指定した方が有効になるみたいです。
先ほどの「-include」オプションと、併用することもできます。
$ jdeps -regex java.util.* -include org.apache.commons.lang3.text.* commons-lang3-3.3.1/commons-lang3-3.3.1.jar commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3.text (commons-lang3-3.3.1.jar) -> java.util org.apache.commons.lang3.text.translate (commons-lang3-3.3.1.jar) -> java.util
クラスパスを設定する
これは、普通に「-cp」または「-classpath」オプションで指定可能です。
$ jdeps -cp commons-lang3-3.3.1/commons-lang3-3.3.1.jar org.apache.commons.lang3.StringUtils commons-lang3-3.3.1/commons-lang3-3.3.1.jar -> /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar org.apache.commons.lang3 (commons-lang3-3.3.1.jar) -> java.io -> java.lang -> java.nio.charset -> java.text -> java.util -> java.util.regex
なかなか面白いツールが導入されましたが、関連として時間があれば以下も見てみようかと思います。
こちらは、Java 8には関係ないですけどね…。
forbidden-apis
https://code.google.com/p/forbidden-apis/