5

意图

我正在使用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到每个遇到的. 它的要点是:ClassLoaderdefineClass

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,并且pdProtectionDomain据我所知)应该是“默认值” ProtectionDomain。所以我看不到这条线导致NullPointerException. 目前我很困惑,我希望有人可以对此提供一些见解。

什么可能导致defineClass以这种方式失败?如果没有明显的解决方案,您能否为我的整体问题提供一种潜在的替代方法?

4

2 回答 2

4

尝试在引导类加载器中加载包含 MyFancyProfiler 的 jar,而不是向 ClassLoaders 注入代码。最简单的方法是将以下行添加到 javaagent jar 的清单中:

Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar

这将使所有类加载器都可以访问该 jar 中的所有内容,包括我相信 OSGi 和朋友。

于 2013-04-17T05:44:47.530 回答
0

你得到了NullPointerException因为this在这种情况下是空的。如果您想使用引导类加载器(即)加载一个类,您需要通过使用作为 ClassLoader 和作为 ProtectionDomain的方法null来绕过安全检查。sun.misc.Unsafe.defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);nullnull

于 2020-10-16T00:02:17.270 回答