意图:
我正在使用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
的一部分)中初始化的相对复杂系统的入口点。
编辑 - MyFancyProfiler
包含一个静态API,它将通过类似in the solution to this question所述的机制获取对系统其余部分的引用。引用是作为Object
获得的,并且通过反射进行适当的调用,这样即使当前的ClassLoader不知道底层类,它仍然可以工作。
难点
对于一个简单的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时遇到的至少两个单独的ClassLoader(从命令行启动IDE),我从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
似乎是在构造函数时初始化的集合,pd
是ProtectionDomain
(据我所知)应该是“默认”ProtectionDomain
。所以我没有看到该行明显的方式导致NullPointerException
。目前我很难过,我希望有人可以提供一些见解。
什么可能导致defineClass
以这种方式失败?如果没有明显的解决方案,您能为我的整体问题提供一种潜在的替代方法吗?
答案 0 :(得分:4)
不要将代码注入ClassLoaders,而是尝试在bootstrap类加载器中加载包含MyFancyProfiler的jar。最简单的方法是将以下行添加到javaagent jar的清单中:
Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar
这将使所有类加载器都能访问该jar中的所有内容,包括我相信OSGi和朋友。
答案 1 :(得分:0)
您得到NullPointerException
,因为在这种情况下this
为空。如果要使用引导程序类加载器(null
)加载类,则需要使用sun.misc.Unsafe.defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
作为类加载器,而将null
作为类加载器,使用null
方法来绕过安全性检查。 ProtectionDomain。