意图:
我正在使用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
)。
编辑-包含一个静态 API,它将通过类似于此问题的解决方案中MyFancyProfiler
描述的机制获得对系统其余部分的引用。引用以 . 的形式获得,并通过反射进行适当的调用,因此即使当前的 ClassLoader 不知道底层类,它仍然可以工作。Object
困难
对于一个简单的 Java 程序,该方法可以正常工作。对于“真正的”应用程序(如窗口应用程序,尤其是RCP/OSGi应用程序),我遇到了ClassLoaders
. 有些ClassLoaders
人不知道如何找到这个MyFancyProfiler
类,所以当它试图调用MyFancyProfiler
.
我对此的解决方案(以及我真正的问题正在发生的地方)目前是通过反射调用“注入”MyFancyProfiler
到每个遇到的. 它的要点是:ClassLoader
defineClass
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
是 JRE 系统类,所以它似乎很好。
问题
这甚至有效!大多数时候!但是对于我在尝试跟踪 Eclipse(从命令行启动 IDE)时遇到的至少两个单独的 ClassLoader,我NullPointerException
从方法内部得到了一个ClassLoader.defineClass
:
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
似乎是在构造函数时初始化的 Set,并且pd
(ProtectionDomain
据我所知)应该是“默认值” ProtectionDomain
。所以我看不到这条线导致NullPointerException
. 目前我很困惑,我希望有人可以对此提供一些见解。
什么可能导致defineClass
以这种方式失败?如果没有明显的解决方案,您能否为我的整体问题提供一种潜在的替代方法?