[英]Java Bytecode Instrumentation: NullPointerException in reflective call to defineClass
意圖:
我正在使用java.lang.instrument包為 Java 程序創建一些工具。 這個想法是我通過這個系統使用字節碼操作,以便在每個方法的開頭和結尾添加方法調用。 一般來說,經過修改的 Java 方法如下所示:
public void whateverMethod(){
MyFancyProfiler.methodEntered("whateverMethod");
//the rest of the method as usual...
MyFancyProfiler.methodExited("whateverMethod");
}
MyFancyProfiler
是一個相對復雜的系統的入口點,它在premain
方法(它是java.lang.instrument
一部分)期間被初始化。
edit - MyFancyProfiler
包含一個靜態 API,它將通過類似於此問題的解決方案中描述的機制獲取對系統其余部分的引用。 引用作為Object
,並通過反射進行適當的調用,因此即使當前 ClassLoader 不知道底層類,它仍然可以工作。
困難
對於簡單的 Java 程序,該方法工作正常。 對於“真正的”應用程序(如窗口應用程序,尤其是RCP/OSGi應用程序),我遇到了ClassLoaders
問題。 一些ClassLoaders
不知道如何找到MyFancyProfiler
類,因此當它嘗試調用MyFancyProfiler
的靜態方法時會拋出異常。
我對此的解決方案(以及我真正的問題發生的地方)目前是通過反射性調用defineClass
將MyFancyProfiler
“注入”到每個遇到的ClassLoader
。 它的要點是:
public byte[] transform(ClassLoader loader, String className, /* etc... */) {
if(/* this is the first time I've seen loader */){
//try to look up `MyFancyProfiler` in `loader`.
if(/* loader can't find my class */){
// get the bytes for the MyFancyProfiler class
// reflective call to
// loader.defineClass(
// "com.foo.bar.MyFancyProfiler", classBytes, 0, classBytes.length);
}
}
// actually do class transformation via ASM bytecode manipulation
}
編輯以獲取更多信息- 這種注入的原因是為了確保每個類,無論是哪個 ClassLoader 加載它,都能夠直接調用MyFancyProfiler.methodEntered
。 一旦調用, MyFancyProfiler
將需要使用反射與系統的其余部分進行交互,否則當它嘗試直接引用時,我將收到 InvocationTargetException 或 NoClassDef 異常。 我目前讓它工作,因此MyFancyProfiler
唯一的“直接”依賴MyFancyProfiler
是 JRE 系統類,所以它似乎沒問題。
問題
這甚至有效! 大多數時候! 但是對於我在嘗試跟蹤 Eclipse(從命令行啟動 IDE)時遇到的至少兩個單獨的 ClassLoader,我從ClassLoader.defineClass
方法內部得到一個NullPointerException
:
java.lang.NullPointerException
at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:500)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
// at my code that calls the reflection
ClassLoader.java的500行是調用domains.add(pd)
其中domains
似乎是一組,使程序在構造函數時初始化,以及pd
是一個ProtectionDomain
是(據我可以告訴)應該是“默認“ ProtectionDomain
。 所以我沒有看到該行導致NullPointerException
的明顯方式。 目前我很難過,我希望有人可以對此提供一些見解。
什么會導致defineClass
以這種方式失敗? 如果沒有明顯的解決方案,您能否為我的整體問題提供一種潛在的替代方法?
不要將代碼注入到類加載器,而是嘗試在引導類加載器中加載包含 MyFancyProfiler 的 jar。 最簡單的方法是將以下行添加到您的 javaagent jar 的清單中:
Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar
這將使所有類加載器都可以訪問該 jar 中的所有內容,包括我相信 OSGi 和朋友。
你得到NullPointerException
因為在這種情況下this
是空的。 如果要使用引導類加載器(為null
)加載類,則需要使用sun.misc.Unsafe.defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
繞過安全檢查sun.misc.Unsafe.defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
使用null
作為 ClassLoader 和null
作為 ProtectionDomain 的方法。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.