2
0

More than 3 years have passed since last update.

【Java】classファイルからクラスの情報を取得してみる

Posted at

概要

Javaはビルドを行うとclassファイルを出力します。このclassファイルの中身をあえて解析しようという人は、あまりいないかもしれません。がしかし、ソースコードの分析とかに役立つかもということで、今回は試しにclassファイルの情報を取得してみたので、その方法を紹介してみたいと思います。

今回取得する情報

  • java.lang.Classで取得できるものを取得します。こちらの記事で紹介されているようにフィールド名やメソッド名など、クラスに関する基本的な情報は一通り取得できます。
  • javapで取得できるものを取得します。javapではクラスファイルはすべからくjavapしようの記事で紹介されている通り、ファイルサイズやチェックサム、コンパイルしたバージョンやコンスタントプールなどが参照できます。コンスタントプールについてはこちらの記事が詳しいです。コンスタントプールでは、呼出元から何のクラス・メソッドが、呼ばれているかの情報が取得できたりします。

処理の流れと参考記事

実装サンプル

【実行クラス】

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;
    }
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0