概要
Javaはビルドを行うとclassファイルを出力します。このclassファイルの中身をあえて解析しようという人は、あまりいないかもしれません。がしかし、ソースコードの分析とかに役立つかもということで、今回は試しにclassファイルの情報を取得してみたので、その方法を紹介してみたいと思います。
今回取得する情報
-
java.lang.Class
で取得できるものを取得します。こちらの記事で紹介されているようにフィールド名やメソッド名など、クラスに関する基本的な情報は一通り取得できます。 -
javap
で取得できるものを取得します。javapではクラスファイルはすべからくjavapしようの記事で紹介されている通り、ファイルサイズやチェックサム、コンパイルしたバージョンやコンスタントプールなどが参照できます。コンスタントプールについてはこちらの記事が詳しいです。コンスタントプールでは、呼出元から何のクラス・メソッドが、呼ばれているかの情報が取得できたりします。
処理の流れと参考記事
- classファイルが出力されるパスと、取得したいクラス情報のパッケージを指定して、その配下のパッケージのclassファイルのパスを取得します。ディレクトリ情報を取得する実装は【Java入門】ディレクトリ内のファイルを再帰的に取得する方法の記事が参考になります。
- 取得したclassファイルのパスから
ClassLoader
を使用して、java.lang.Class
を取得します。
ClassLoaderからの読み込みについては、実行時にクラスファイルを読み込むクラスローダーの記事が参考になります。 - Runtimeクラスを使って
javap
を実行し結果を取得します。結果の解析については割愛し、今回は純粋に文字列として結果取得出来るまでとします。Runtimeクラスの詳細については外部プロセスの起動(Runtime#exec)が参考になります。
実装サンプル
【実行クラス】
Executer.java
public class Executer {
public static void getClassInfo(String classFilePath, String packageName) {
try {
// ディレクトリとパッケージを指定しクラスファイルのパスを取得
List<String> classFilePaths = DirectoryService.getClassFilePathList(classFilePath, packageName);
// クラスファイルからjava.lang.Classの情報を取得
ClassLoaderService classLoaderService = new ClassLoaderService();
List<Class> classes = classFilePaths.stream().map(path -> {
try {
return classLoaderService.getClassFromFilePath(path);
} catch (Exception e) {
return null;
}
}).filter(c -> c != null).collect(Collectors.toList());
// クラスファイルからjavapの情報を取得
List<String> javapResults = classFilePaths.stream().map(path -> {
try {
return classLoaderService.getJavap(path);
} catch (Exception e) {
return null;
}
}).filter(c -> c != null).collect(Collectors.toList());
} catch (Exception e) {
e.printStackTrace();
}
}
}
【classファイルのパス一覧取得】
DirectoryService.java
public class DirectoryService {
public static List<String> getClassFilePathList(String targetPath, String packageName) throws Exception {
// パッケージ名をパス形式に変換
String packageDirectory = packageName.replaceAll("\\.", "/");
try(Stream<Path> stream = Files.walk(Paths.get(targetPath))) {
return stream.filter(path -> {
String pathString = path.toString();
// パス配下かつクラスファイル
return pathString.contains(packageDirectory) && pathString.endsWith(".class");
}).map(path -> path.toString()).collect(Collectors.toList());
} catch(Exception e) {
throw e;
}
}
}
【java.lang.Classの結果取得】
ClassLoaderService.java
public class ClassLoaderService extends ClassLoader {
private static final int BUF_SIZE = 1024;
public Class getClassFromFilePath(String targetPath) throws Exception {
FileInputStream in = new FileInputStream(targetPath);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[BUF_SIZE];
int len = in.read(buf);
while(len != -1) {
out.write(buf, 0, len);
len = in.read(buf);
}
byte[] loadedData = out.toByteArray();
Class loadedCLass = defineClass(null, loadedData, 0, loadedData.length);
return loadedCLass;
}
}
【javapの結果取得】
ClassLoaderService.java
public class ClassLoaderService extends ClassLoader {
public String getJavap(String targetPath) throws Exception {
ProcessBuilder pb = new ProcessBuilder(
"javap", "-v", targetPath.replaceAll("\\.class", "")
);
Process p = pb.start();
InputStream stdIn = p.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdIn));
InputStream errIn = p.getErrorStream();
BufferedReader errReader = new BufferedReader(new InputStreamReader(errIn));
String result = "";
try {
String str = reader.readLine();
while(str != null){
result += str;
str = reader.readLine();
}
str = errReader.readLine();
while(str != null){
result += str;
str = errReader.readLine();
}
int ret = p.waitFor();
if (ret != 0) {
throw new IOException("ret faliled");
}
} finally {
reader.close();
errReader.close();
stdIn.close();
errIn.close();
}
return result;
}
}