檢視執行時生成的檔案,以更清楚執行情況。
檢視動態生成的類,一般有兩個方法:
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
相信在必要的時候,可以派上用場!