Javaパッケージ間の循環参照を検出するライブラリ depDetect

2018年8月27日

※GUI付の循環参照検出ツールdepDetectGuiはJavaパッケージ間の循環依存を検出するツールdepDetectGuiの紹介にある。弊社製オープンソースソフトはOpen Sourcesとしてまとめているので参照されたい。

※以下は、検出用ライブラリについての説明であり、これにGUIは付属していない。

Javaパッケージ間の循環依存を検出するで書いたことだが、適当に記述された大規模なJavaプログラムがあり、そのもつれあった循環参照を排除したい。

これをJDKバンドルのjdepsコマンドを呼び出して行う。

以下の記述は1.4.3時点のものである。

サンプル出力

このライブラリを利用して、このライブラリ自体のソースコードの解析を行わせ、適当に出力させてみたものが以下である(わざと循環依存を起こすようなコードにしてある)。

不明import
java.io
java.lang
java.lang.invoke
java.nio.charset
java.nio.file
java.util
java.util.concurrent
java.util.function
java.util.regex
java.util.stream
org.hamcrest
org.junit
org.junit.runner
org.junit.runners

木構造
com
 cm55
  depDetect
   impl
    AllTest
    BinTreeCreator
    BinTreeDetail
    BinTreeDetailTest
    BulkImports
    ClsDeps
    ClsNodeImpl
    CommentRemover
    CommentRemoverCommentTest
    CommentRemoverTest
    DepsBuilder
    Import
    ImportTest
    Imports
    JavaNodeImpl
    PkgNodeImpl
    PkgNodeImplTest
    RefsImpl
    SrcImportExtractor
    SrcImportExtractorTest
    SrcTreeCreator
    SrcTreeDetail
    UnknownsImpl
    VarImports
    package-info
   test
    Main
   ClsNode
   CyclicTest
   JavaNode
   JavaNodeKind
   PkgNode
   Refs
   Unknowns
   VisitOrder
   package-info


循環依存発生パッケージ:com.cm55.depDetect
 依存先パッケージ:com.cm55.depDetect.impl
  原因クラス:com.cm55.depDetect.CyclicTest
  原因クラス:com.cm55.depDetect.test.Main

循環依存発生パッケージ:com.cm55.depDetect.impl
 依存先パッケージ:com.cm55.depDetect
  原因クラス:com.cm55.depDetect.impl.BinTreeCreator
  原因クラス:com.cm55.depDetect.impl.BinTreeDetail
  原因クラス:com.cm55.depDetect.impl.BinTreeDetailTest
  原因クラス:com.cm55.depDetect.impl.ClsNodeImpl
  原因クラス:com.cm55.depDetect.impl.DepsBuilder
  原因クラス:com.cm55.depDetect.impl.JavaNodeImpl
  原因クラス:com.cm55.depDetect.impl.PkgNodeImpl
  原因クラス:com.cm55.depDetect.impl.PkgNodeImplTest
  原因クラス:com.cm55.depDetect.impl.RefsImpl
  原因クラス:com.cm55.depDetect.impl.SrcTreeCreator
  原因クラス:com.cm55.depDetect.impl.SrcTreeDetail
  原因クラス:com.cm55.depDetect.impl.UnknownsImpl

仕組み

仕組みとしてはこうだ。

一つまたは複数の.classフォルダを指定することにより、それについてjdepsを走行させる。jdepsはそれぞれのクラスの依存を標準出力に出力するので、これをパッケージごとの他パッケージへの依存としてまとめる。

これにより、ある一つのパッケージについて「依存するパッケージ」と「依存されているパッケージ」の集合が得られる。

それらの積集合が「循環依存しているパッケージ」になる。AはBに依存しており、BはAに依存しているという状態だ。

ただし、実際に依存を発生させているのはパッケージ自体ではなく、その中に含まれるクラスであるため、その依存を発生させているクラスが特定できるようなデータ構造になっている。

簡単な使い方

このライブラリ自体のJavaソースを対象として、冒頭の出力を得るサンプルは以下になる。

package com.cm55.depDetect.test;
import java.io.*;
import java.util.*;

import com.cm55.depDetect.*;
import com.cm55.depDetect.impl.*;

public class Main {
  public static void main(String[] args) throws IOException {

    PkgNode root = BinTreeCreator.create(
      null,
      Arrays.stream(new String[] {
        "C:\\devel\\workspace-neon\\github_depDetect\\bin\\default",
      })
    );

    // 不明importを表示
    System.out.println("不明import");
    root.getUnknowns(true).stream().forEach(System.out::println);

    // 木構造を表示
    System.out.println("\n木構造");
    System.out.println(root.treeString());

    // 循環参照を表示
    root.visitPackages(VisitOrder.PRE, pkg -> {
      Refs cyclics = pkg.getCyclics(false);
      if (cyclics.size() == 0)
        return;
      System.out.println("\n循環依存発生パッケージ:" + pkg);
      cyclics.stream().forEach(cyc-> {
        System.out.println(" 依存先パッケージ:" + cyc);
        pkg.childClasses(true).forEach(cls-> {
          if (cls.getDepsTo().contains(cyc)) {
            System.out.println("  原因クラス:" + cls);
          }
        });
      });
    });    

  }
}