在Java Instrumentation Agent中使用Class.forName()

时间:2017-10-02 09:38:32

标签: java classloader instrumentation

据我所知,如果我使用:

Instrumentation#getAllLoadedClasses()

我确实可以通过目标JVM选择所有已加载的类。但如果我这样做:

Class.forName("my.class.name")

这与VM加载的类不同。是的,我可以在代理MANIFEST.MF Class-Path中将此特定类添加为jar - 但这对我来说与getAllLoadedClasses()看起来不一样。

有人可以确认这是否正确,即在使用仪器时我无法使用Class.forName()查找特定课程?我的目标不是使用getAllLoadedClasses()迭代所有加载的类 - 但如果没有其他选择,我想现在还可以。

** 更新

我写错了的是Boot-Class-Path我现在已经在我的清单中纠正过了。使用-verbose:class日志记录,我设法看到我的jar正在加载为

[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]

但我没有看到任何相应的加载信息。我尝试添加Class.forName("a.package.in.someother.jar.classname")并获得NoClassDefFoundError。一旦我跳进代理jar,我就不能使用Class.forName()来检查目标VM是否加载了类。我得到一个NoClassDefFoundError。

进一步更新

好的,我已经"肥胖"清单查找我WEB-INF/lib和tomcat' lib目录中的所有类。我能看到的是:

1)第一次加载我的自定义类MyClass时。 -verbose显示:

[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]

2)如果我再次尝试加载课程,则会正确显示上述顺序。

3)我的代理jar表示我的tomcat lib和我的web-inf/lib目录的所有类。我还可以确认装载机正确看到了罐子。

4)现在我注入代理,并从代理类中调用Class.forName("my.pkg.MyClass")。我得到以下结果。

[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]

我承认它的系统类加载器将其编入我的代理代码中,正如@RafaelWinterhalter在他的一个答案中指出的那样。有什么方法可以强迫一个"代表团"这样不同的类加载器就会加载代理类,因此正确地重新定义了一个类。

感谢任何帮助。

3 个答案:

答案 0 :(得分:1)

正如javadoc中所述:

  

调用此方法相当于:   Class.forName(className, true, currentLoader)   其中currentLoader表示定义的类加载器   现在的班级。

您还可以从源代码中看到该方法已标记为@CallerSensitive,这意味着您将根据调用该方法的类加载器获得不同的结果。

当调用Instrumentation::getAllLoadedClasses时,返回的数组包含任何类加载器的类,而不仅仅是当前类加载器的类,它是运行Java代理时的系统类加载器。因此:

for (Class<?> type : instrumentation.getAllLoadedClasses()) {
  assert type == Class.forName(type.getName());
}

通常不正确。

答案 1 :(得分:0)

经过一段时间的奔波,感谢@Holger提醒我问题是什么 - 错误的类加载器。

在我注射代理之前,我做了以下事情:

// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();

// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());

// Now inject agent
try {
    vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
  vm.detach();
  // Put back the classLoader linkage
  sc.set(null, original);
}

我如何确认

  1. 当它进入我的Agent类时 - Thread.currentThread().getContextClassLoader()成为我的应用程序extn加载器。但是系统类加载器现在变成了“ParallelWebappClassLoader”。

  2. 我假设这是它的工作方式,但可能完全可以接受:

    i)当我说Class.forName("my.pkg")时,它会检查指向我的加载器的系统类加载器。如果没有找到该类(即没有加载),它将转到父母等。我相信这或多或少是代表模型。

    ii)通过这种方式,类在同一个类加载器中加载到VM中,在正常情况下也会加载我的webapp中的类。

    iii)除了我自己的课程之外,我不会做任何其他工具,所以类加载器总是一样的。

  3. 到目前为止,我还没有看到任何LinkageError发生。但是我仍然觉得这太冒险了,如果我打破了这个环节,我就搞砸了。

答案 2 :(得分:0)

必须避免在Java事件探查器中使用Class.forName来避免NoClassDef错误。 JVM根据其类路径设置和类文件需求在不同级别的类加载器中加载类文件。

Java Cre库+引导路径保护的库将在引导程序级别中加载
Java代理将被加载到系统级别并继续运行。 Class.forName()将从父加载器中查找类文件,当前加载器将不检查子加载器(除非我们实现自己的加载器,否则将一直保留)

您可以从您的应用程序代码访问Java核心类,但是Java核心类将无法访问我们的应用程序代码。它称为类加载器层次结构。

您有三个选择。

  1. 从Instrumentation.GetLoadedClassFiles()中查找类文件
  2. 通过Transformers,您可以获得所有的加载器类,并且可以跟踪它们并在每个加载器中查找您的类,直到找到为止。
  3. 将Class.forname实现放在层次结构的最底层,以便它可以在内部访问所有路径。

正确维护层次结构,避免出现太多奇怪的错误。