mavenなどでいろいろなフレームワークを試してみるときに
依存するプロジェクトがそれぞれ違うバージョンのlog4jなどに依存している場合があります。
単一バージョンのlog4jのみを使用するように、うまくpom.xmlファイルでexlcudeされていれば問題ないです。
しかしそうなっていない場合、コンパイルは通るものの、実行時に難解な例外やエラーが出てしまいます。
そのときに一度試してみて頂きたいのが下記の方法で、複数のjarに重複するパッケージ・クラスを持ったものがいないか探し出す方法です。
もっとよい方法があれば教えてください。お願いします。
###テスト環境
Java 17
eclipse 2022-06
###サンプル環境構成
project
testprj
│
├─libs
│ └─log4j
│ ├─1.2.12
│ │ log4j-1.2.12.jar
│ │
│ ├─1.2.14
│ │ log4j-1.2.14.jar
│ │
│ ├─1.2.16
│ │ log4j-1.2.16.jar
│ │
│ └─1.2.17
│ log4j-1.2.17.jar
│
└─src
└─jp
└─co
└─panda
└─testprj
Main.java
バージョン違いのLog4jのjarをlib以下に配置し、クラスパスに追加します。
###サンプルコード
実行するOSによりクラスパスの区切り文字を変更してください。
エラー処理などは手を抜いています。あしからず。
Main.java
package jp.co.panda.testprj;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
private static final String CLASS_PATH_SEPARATOR = ";";//windows
public static void main(String[] args) {
// クラスパスを取得する
final String[] classPathArray = System.getProperties()
.getProperty("java.class.path", null)
.split(CLASS_PATH_SEPARATOR);
// jar名称とjarに含まれるクラスを調べてリストにする
List<ClassPathData> classpathDataList = Stream.of(classPathArray)
.filter(classpath -> classpath.endsWith(".jar"))
.map(classpath -> {
try (JarFile jarFile = new JarFile(new File(classpath))) {
// jar名を取得する
String[] sp = classpath.split("\\\\");
final String jarName = sp[sp.length - 1];
// jar内に含まれているクラス情報を取得する
// META-INFとかは除外している
List<ClassPathData> dataList = Collections.list(jarFile.entries()).stream()
.map(jarEntry -> jarEntry.toString())
.filter(jarEntry -> jarEntry.endsWith(".class"))
.map(jarEntry -> {
return new ClassPathData(
jarName,
jarEntry
);
})
.collect(Collectors.toList());
return dataList.stream();
} catch (IOException e) {
throw new RuntimeException();
}
})
.flatMap(stream -> stream)
.collect(Collectors.toList());
// フルクラス名でグルーピングし、2個以上重複しているものを出力する
classpathDataList.stream()
.collect(Collectors.groupingBy(ClassPathData::getClasspath))
.entrySet()
.stream()
.filter(entry -> entry.getValue().size() > 1)
.sorted((a, b) -> a.getValue().size() - b.getValue().size())
.sorted((a, b) -> a.getKey().compareTo(b.getKey()))
.forEach(entry -> {
System.out.println(entry.getKey());
entry.getValue().stream()
.map(data -> data.getJarname())
.map(str -> "\t" + str)
.sorted()
.forEach(System.out::println);
});
}
/**
* jar名称とjarに含まれるクラスを保持する
*/
static class ClassPathData {
private String jarname;
private String classpath;
public ClassPathData(String jarname, String classpath) {
this.jarname = jarname;
this.classpath = classpath;
}
public String getJarname() {
return jarname;
}
public void setJarname(String jarname) {
this.jarname = jarname;
}
public String getClasspath() {
return classpath;
}
public void setClasspath(String classpath) {
this.classpath = classpath;
}
}
}
このコードを実行することで、出力されたログにはクラスが重複しているjar名をクラス単位に表記します。
org/apache/log4j/Appender.class
log4j-1.2.12.jar
log4j-1.2.14.jar
log4j-1.2.16.jar
log4j-1.2.17.jar
org/apache/log4j/AppenderSkeleton.class
log4j-1.2.12.jar
log4j-1.2.14.jar
log4j-1.2.16.jar
log4j-1.2.17.jar
org/apache/log4j/AsyncAppender$DiscardSummary.class
log4j-1.2.14.jar
log4j-1.2.16.jar
log4j-1.2.17.jar
org/apache/log4j/AsyncAppender$Dispatcher.class
log4j-1.2.14.jar
log4j-1.2.16.jar
log4j-1.2.17.jar
...以降省略