Java 字节码检测:反射调用中的 NullPointerException 定义类

Dyl*_*lan 5 java instrumentation osgi classloader jvmti

意图

我正在使用java.lang.instrument包为 Java 程序创建一些工具。这个想法是我通过这个系统使用字节码操作,以便在每个方法的开头和结尾添加方法调用。一般来说,经过修改的 Java 方法如下所示:

public void whateverMethod(){
    MyFancyProfiler.methodEntered("whateverMethod");
    //the rest of the method as usual...
    MyFancyProfiler.methodExited("whateverMethod");
}
Run Code Online (Sandbox Code Playgroud)

MyFancyProfiler是一个相对复杂的系统的入口点,该系统在premain方法(它是 的一部分java.lang.instrument)期间被初始化。

编辑-MyFancyProfiler包含一个静态 API,它将通过类似于此问题的解决方案中描述的机制获取对系统其余部分的引用。引用作为 获取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
}
Run Code Online (Sandbox Code Playgroud)

编辑以获取更多信息- 这种注入的原因是为了确保每个类,无论哪个类加载器加载它,都能够MyFancyProfiler.methodEntered直接调用。一旦进行了该调用,MyFancyProfiler就需要使用反射与系统的其余部分进行交互,否则当它尝试直接引用时,我将收到 InvocationTargetException 或 NoClassDef 异常。我目前让它工作,所以唯一的“直接”依赖MyFancyProfiler是 JRE 系统类,所以它似乎没问题。

问题

这甚至有效!大多数时候!但是对于我在尝试跟踪 Eclipse(从命令行启动 IDE)时遇到的至少两个单独的类加载器,我NullPointerExceptionClassLoader.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
Run Code Online (Sandbox Code Playgroud)

500线ClassLoader.java是一个电话domains.add(pd),在那里domains似乎是被在构造时初始化一个集合,pd是一个ProtectionDomain即(据我可以告诉)应该是“默认” ProtectionDomain。所以我没有看到该行导致NullPointerException. 目前我很难过,我希望有人可以对此提供一些见解。

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

ada*_*daf 4

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

Boot-Class-Path: fancy-profiler-bootstrap-stuff.jar
Run Code Online (Sandbox Code Playgroud)

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