2014-10-29 50 views
0

我有以下場景,我有一個類加載器和它加載的類,現在我需要該類的字節碼。以下是我迄今爲止嘗試:爲每個類文件Java:給定類加載器和類,得到類字節代碼

Field f = ClassLoader.class.getDeclaredField("classes"); 
    f.setAccessible(true); 

    ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 
    Vector<Class> classes = (Vector<Class>) f.get(classLoader); 

    for(Class loadedClass : classes) 
    { 
     String className = loadedClass.getName(); 
     String classFileResourcePath = "/" + className.replace(".", "/") + ".class"; 
     InputStream inputStream = classLoader.getResourceAsStream(classFileResourcePath); 
     System.out.println(">>>> " + className + " => " + classFileResourcePath + " => " + inputStream); 
    } 

此代碼打印null。但是,當我將其更改爲classLoader.getClass().getResourceAsStream(classFileResourcePath)時,它在IDE中的獨立Main類中運行時會有效,但當我需要實際上下文時,它也會返回null,這可能是因爲有「特殊」情況發生罐子和幕後的班級。如果不能討論這些細節,只需說出我所擁有的是一個類和加載它的類加載器,現在我需要字節代碼。我該怎麼做呢?如果這在Java層中是不可能的,我可能能夠獲取原始Jar本身並將其作爲zip文件讀取,但這是最後的選擇。

回答

-1

你應該看看ASM library

與庫可以訪問字節碼是這樣的:

ClassReader cr = new ClassReader("java.lang.Runnable"); 
ClassNode cn = new ClassNode(); 
cr.accept(cn, 0); 

然後你就可以訪問使用ClassNode的干將依據的信息對象。 使用訪問者的基於事件的分析也是可能的。

請注意,您可以使用輸入流或字節數組實例化ClassReader,而不是類名稱。

+0

很感謝,我可以使用ASM。問題:你如何從節點獲取原始字節? – 2014-10-29 17:49:21

+0

你想要檢索什麼信息?可能你可以訪問''ClassNode.method'',循環這些條目並將它們轉換爲''MethodNode''。每個「MethodNode」都包含它的指令列表。 – nrainer 2014-10-29 17:53:23

+0

哈哈,不,我真的需要字節,不要問。這似乎工作得到字節''ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); classNode.accept(classWriter); byte [] bytes = classWriter.toByteArray();'我正在嘗試結果在真正的環境。 – 2014-10-29 18:03:14

2

實際上有幾個問題與您的代碼示例:

首先,您可以訪問java.lang.ClassLoader類的「類」字段,以確定哪些類已經裝載。這是一個專用字段,如果您讓代碼在使用專用類加載器(java.lang.ClassLoader的子類)的環境中運行,則您或多或少不知道該字段中包含的內容。

使用ClassLoader.getResourceAsStream,您在路徑前加了一個「/」,這是不正確的。 ClassLoader.getResourceAsStream需要絕對路徑並且路徑以第一段的名稱開始,例如使用ClassLoader.getResourceAsStream("java/lang/ClassLoader.class")而不是ClassLoader.getResourceAsStream("/java/lang/ClassLoader.class")

使用Class.getResourceAsStream,可以提供以「/」開頭的絕對路徑,也可以提供相關類的路徑,而不是以「/」開頭。例如。 ClassLoader.class.getResourceAsStream("ClassLoader.class")ClassLoader.class.getResourceAsStream("/java/lang/ClassLoader.class")通常都會讓你訪問類的字節碼。

但是,兩種方法都要求使用Java運行時環境的標準命名約定,類文件作爲類路徑上的資源提供。沒有要求Java運行時環境必須這樣操作。 Java類可能會動態生成,導致它們被類加載器所知,但不會由持久字節碼支持。專有類加載器也不需要在類名稱和資源路徑之間使用與標準類加載器相同的映射。

Java類加載器也不提供公共API來訪問類的字節代碼。如果您將VM分成「本機代碼部分」和「Java代碼部分」,則很明顯VM通常不需要引用「Java代碼部分」中的原始字節代碼。

依靠標準類加載器使用的約定,您可以使用您的方法,它將主要在獨立應用程序中工作。但是,當你發現自己的時候,如果你在不同的環境中運行代碼,它可能會失敗,例如當部署到應用程序服務器或使用OSGi等打包框架時。

+0

這是一個很好的答案。 tl; dr:刪除「/」前綴,並且要注意,這種方法僅適用於不是動態生成的類以及使用標準類名稱 - >文件名映射的類加載器。 – 2014-10-30 13:48:57

+0

是的,我完全知道以上是做錯的方法。事實證明,我在一個特殊的類加載器編程的環境中是需要的。 – 2014-11-03 22:58:10

1

優選的方法是Class.getResourceClass.getResourceAsStream。這將自動使用正確的ClassLoader(或使用ClassLoader.getSystemResource(),如果ClassLoadernull)。它還將解析該類的package內的資源,除非您將資源名稱預先加上'/'

所以對於一個Class對象不是代表一個嵌套類,你可以要求使用theClass.getResourceAsStream(theClass.getSimpleName()+".class")

相關資源如果您需要內部類的正確處理,您將通過Class.getName()得到合格的名稱,並使用或者改造它'/'+name.replace('.', '/')+".class")name.substring(name.lastIndexOf('.')+1)+".class")

如果失敗,則ClassLoader不支持獲取類的字節碼或類已經在即時生成並沒有增加在某種程度上ClassLoader可以使用記錄的字節碼。


如果你希望能夠即使是這樣的類來檢索字節代碼,你需要一個JVM支持Instrumentation。 A ClassFileTransformer將得到字節碼作爲輸入,因此可以將其存儲在某個地方而不實際轉換它,如果這是意圖的話。

另請參閱Instrumentation.getInitiatedClasses(java.lang.ClassLoader)以獲得特定ClassLoader的類的可靠方法。

但是,你應該知道,這不是必然傳遞給defineClass爲JVM可能會剝離不相關信息的執行,也將數據存儲在一個優化的形式創建一個等價的,但不完全匹配的字節代碼的字節碼將其轉換回傳遞給變壓器時。

另一個需要注意的是,如果在JVM中註冊了其他變換器,例如如果您同時使用儀器分析器,則您無法精確控制變壓器的順序。即第一個轉換器將會看到等同於存儲在磁盤上的代碼的字節碼,而最後一個鏈將看到與JVM最終執行的代碼相當的代碼,而中間轉換器則看到可能不匹配它們的東西。

請注意,即使使用getResourceAsStream,字節碼也不需要匹配,例如,如果因爲defineClass被調用而導致底層資源被修改。原則上,ClassLoader並不強制執行loadClass/findClassgetResourceAsStream以一致的方式。

+0

注意:Instrumentation API僅適用於Java代理。 [本答案](http://stackoverflow.com/a/19912148/2711488)顯示瞭如何在應用程序可以通過將自己作爲Java代理啓動時訪問此API,前提是JVM支持在運行時附加代理。另請參閱http://stackoverflow.com/a/19496211/2711488和http://stackoverflow.com/q/18322117/2711488 – Holger 2014-10-30 11:28:39

+0

使用Instrumentation非常聰明,但無法保證您的ClassFileTransformer將運行「第一次」(如果你真的想要「原始」類字節)或「最後」(如果你想要JVM實際使用的類字節)。 – 2014-10-30 13:50:45

+0

@bkail:對,但是這只是一個問題,在這種情況下*有*其他變形金剛不是常見的情況。提問者沒有給我們足夠的信息來推斷這是否是一個問題。 – Holger 2014-10-30 14:28:59

0

正如@jarnbjo所提到的,這不是一般的工作方法。我正在尋找一種通用的方法。我發現了兩種有前途的方法,並且只有一種實際工作方法:

a。 Instrumentation API。這工作。我決定在嘗試修改某些類時由於困難而不使用它。工具代理運行在相同的JVM中,當它試圖檢測它所依賴的類時,可能會出現一些非常奇怪的異常。 (我已經學到了一些新的異常類型。埃姆,java.lang.ClassCircularityError ...)

這很可能是你 OK,如果你承認自己添加儀器劑(JVM通過參數)時, JVM啓動。你似乎只需要讀取字節碼,所以你不應該得到這樣的麻煩

b。 JDI,Java調試接口。這看起來很有希望。我已經開始編寫一個腳本來重建JDI API中的字節碼。幾乎所有我需要的東西,除了例外表。所以它不是很有用。如果您擁有所有說明,但沒有ExceptionTable屬性,則不能執行任何流分析,反編譯源代碼等。一些異常處理程序看起來像沒有ExceptionTable的死代碼。你只能看到字節碼中的當前位置,沒有一些重要的信息。