如何獲取java執行時動態生成的class檔案?

weixin_34292959發表於2018-09-17

  檢視執行時生成的檔案,以更清楚執行情況。

  檢視動態生成的類,一般有兩個方法:

1. 使用據說是jdk自帶包sa-jdi.jar裡的工具。

其中,不想自己搞,當然就利用下,sa-jdi.jar 裡自帶的的sun.jvm.hotspot.tools.jcore.ClassDump就可以把類的class內容dump到檔案裡。

ClassDump裡可以設定兩個System properties:

  sun.jvm.hotspot.tools.jcore.filter Filter的類名
  sun.jvm.hotspot.tools.jcore.outputDir 輸出的目錄

sa-jdi.jar 裡有一個sun.jvm.hotspot.tools.jcore.PackageNameFilter,可以指定Dump哪些包裡的類。PackageNameFilter裡有一個System property可以指定過濾哪些包:sun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList。

所以可以通過這樣子的命令來使用:

sudo java -classpath "$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=sun.jvm.hotspot.tools.jcore.PackageNameFilter -Dsun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList=com.test sun.jvm.hotspot.tools.jcore.ClassDump <pid>

不過,我在windows下並沒有成功過,原因是還要要求我 start SwDbgSrv.exe,搞不了。

其中sa-jdi.jar檔案也不那麼好找呢,不過也能找到!

所以,還不如自己動手,豐衣足食!


2. 自己重寫一個記錄工具,用agent attatch 到程式,然後利用Instrumentation和ClassFileTransformer就可以獲取 到類的位元組碼了。

工具類如下:

package com.xxx.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.Arrays;

/**
 * 動態生成類攔截檢視工具
 *
 * @date 2018/9/15
 */
public class ClazzDumpCustomAgent implements ClassFileTransformer {

    /**
     * 匯出過濾表示式,此處為類名字首, 以 -f 引數指定
     */
    private String filterStr;

    /**
     * 匯出檔案目錄根目錄, 以 -d 引數指定
     */
    private String exportBaseDir = "/tmp/";

    /**
     * 是否建立多級目錄, 以 -r 引數指定
     */
    private boolean packageRecursive;

    public ClazzDumpCustomAgent(String exportBaseDir, String filterStr) {
        this(exportBaseDir, filterStr, false);
    }

    public ClazzDumpCustomAgent(String exportBaseDir, String filterStr, boolean packageRecursive) {
        if(exportBaseDir != null) {
            this.exportBaseDir = exportBaseDir;
        }
        this.packageRecursive = packageRecursive;
        this.filterStr = filterStr;
    }

    /**
     * 入口地址
     *
     * @param agentArgs agent引數
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs: " + agentArgs);
        String exportDir = null;
        String filterStr = null;
        boolean recursiveDir = false;
        if(agentArgs != null) {
            if(agentArgs.contains(";")) {
                String[] args = agentArgs.split(";");
                for (String param1 : args) {
                    String[] kv = param1.split("=");
                    if("-d".equalsIgnoreCase(kv[0])) {
                        exportDir = kv[1];
                    }
                    else if("-f".equalsIgnoreCase(kv[0])) {
                        filterStr = kv[1];
                    }
                    else if("-r".equalsIgnoreCase(kv[0])) {
                        recursiveDir = true;
                    }
                }
            }
            else {
                filterStr = agentArgs;
            }
        }
        inst.addTransformer(new ClazzDumpCustomAgent(exportDir, filterStr, recursiveDir));
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (needExportClass(className)) {
            int lastSeparatorIndex = className.lastIndexOf("/") + 1;
            String fileName = className.substring(lastSeparatorIndex) + ".class";
            String exportDir = exportBaseDir;
            if(packageRecursive) {
                exportDir += className.substring(0, lastSeparatorIndex);
            }
            exportClazzToFile(exportDir, fileName, classfileBuffer);         //"D:/server-tool/tmp/bytecode/exported/"
            System.out.println(className + " --> EXPORTED");
        }
        return classfileBuffer;
    }

    /**
     * 檢測是否需要進行檔案匯出
     *
     * @param className class名,如 com.xx.abc.AooMock
     * @return y/n
     */
    private boolean needExportClass(String className) {
        if(filterStr != null) {
            if(className.startsWith(filterStr)) {
                return true;
            }
            else {
                return false;
            }
        }
        if (!className.startsWith("java") && !className.startsWith("sun")) {
            return true;
        }
        return false;
    }

    /**
     * 執行檔案匯出寫入
     *
     * @param dirPath 匯出目錄
     * @param fileName 匯出檔名
     * @param data 位元組流
     */
    private void exportClazzToFile(String dirPath, String fileName, byte[] data) {
        try {
            File dir = new File(dirPath);
            if(!dir.isDirectory()) {
                dir.mkdirs();
            }
            File file = new File(dirPath + fileName);
            if (!file.exists()) {
                System.out.println(dirPath + fileName + " is not exist, creating...");
                file.createNewFile();
            }
            else {

//                String os = System.getProperty("os.name");        // 主要針對windows檔案不區分大小寫問題
//                if(os.toLowerCase().startsWith("win")){
//                    // it's win
//                }
                try {
                    int maxLoop = 9999;
                    int renameSuffixId = 2;
                    String[] cc = fileName.split("\\.");
                    do {
                        Long fileLen = file.length();
                        byte[] fileContent = new byte[fileLen.intValue()];
                        FileInputStream in = new FileInputStream(file);
                        in.read(fileContent);
                        in.close();
                        if(!Arrays.equals(fileContent, data)) {
                            fileName = cc[0] + "_" + renameSuffixId + "." + cc[1];
                            file = new File(dirPath + fileName);
                            if (!file.exists()) {
                                System.out.println("new create file: " + dirPath + fileName);
                                file.createNewFile();
                                break;
                            }
                        }
                        else {
                            break;
                        }
                        renameSuffixId++;
                        maxLoop--;
                    } while (maxLoop > 0);
                }
                catch (Exception e) {
                    System.err.println("exception in read class file..., path: " + dirPath + fileName);
                    e.printStackTrace();
                }
            }
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        }
        catch (Exception e) {
            System.err.println("exception occur while export class.");
            e.printStackTrace();
        }
    }
}

 

 

寫好工具類後,將其打包為jar包檔案,(如何打包此類請檢視上一篇文章: idea中如何將單個java類匯出為jar包檔案?),假如打包後的檔案命名名 clazzdumpcustagent.jar 。

MENIFEST.MF 檔案內容如下:

Manifest-Version: 1.0
Premain-Class: com.youge.api.ClazzDumpCustomAgent

使用該jar包工具,進行執行時class檔案檢視。

在執行專案時,新增javaagent,進行程式碼匯出

# 在vm引數中,加入該agent
java -javaagent:D:\server-tool\clazzdumpcustagent.jar=-d=D:/server-tool/tmp/bytecode/exported/;-f2=com/alibaba/dubbo;-r xxx

其中:

  -d: 設定匯出檔案的輸出目錄;

  -f: 設定需要匯出的位元組碼的字首;

  -r: 有該引數代表需要進行包目錄的建立,否則生成所有檔案到一個目錄;

然後可以到指定目錄下去檢視生成的位元組碼檔案了。

最後,使用java反編譯工具,檢視 java程式碼就ok了。(可以直接拖進IDE進行解析)

如果不想自己打包,我打了個包放在網上,有需要可自行下載!  https://download.csdn.net/download/nihe123yiyang/10670937

 

相信在必要的時候,可以派上用場!

相關文章