[英]Using Class.forName() in Java Instrumentation Agent
我的理解是,如果我使用:
Instrumentation#getAllLoadedClasses()
我确实通过目标 JVM 选择了所有加载的类。 但如果我这样做:
Class.forName("my.class.name")
这将与 VM 加载的 class 不同的 class。 是的,我可以将这个特定的 class 添加为代理 MANIFEST.MF 类路径中的 jar - 但在我看来这与getAllLoadedClasses()
。
有人可以确认这是否正确,即我无法在检测时使用Class.forName()
找到特定的 class 吗? 我的目标不是使用getAllLoadedClasses()
遍历所有加载的类 - 但如果没有其他选择,我想现在还可以。
**更新
我写的错误是我现在在清单中更正的Boot-Class-Path
。 使用-verbose:class
日志记录我设法看到我的 jars 被加载为
[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 是否加载了 class。 我收到 NoClassDefFoundError。
进一步更新
好的,我已经“增肥”了清单以查找我的WEB-INF/lib
和 tomcat 的lib
目录中的所有类。 我可以看到如下:
1)当我的自定义 class MyClass
首次加载时。 - -verbose
显示:
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
2) 如果我再次尝试加载 class,它会正确显示上述顺序。
3) 我的代理 jar 显示为我的 tomcat lib
和我的web-inf/lib
目录的所有类。 而且我还可以确认加载程序正确地看到了 jars。
4) 现在我注入代理,并从代理 class 中调用Class.forName("my.pkg.MyClass")
。 我得到以下结果。
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
我承认它的系统 class 加载程序将其加载到我的代理代码中,正如@RafaelWinterhalter 在他的一个答案中指出的那样。 有什么办法可以强制“委托”,以便不同的类加载器加载代理 class 并因此正确重新定义 class。
任何帮助表示赞赏。
如javadoc中所述:
调用此方法等效于:
Class.forName(className, true, currentLoader)
,其中currentLoader
表示当前类的定义类加载器。
您还可以从源代码中看到该方法被标记为@CallerSensitive
,这意味着您将基于调用该方法的类加载器获得不同的结果。
当调用Instrumentation::getAllLoadedClasses
,返回的数组包含任何类加载器的类,而不仅包含当前类加载器的类,当前类加载器是运行Java代理时的系统类加载器。 因此:
for (Class<?> type : instrumentation.getAllLoadedClasses()) {
assert type == Class.forName(type.getName());
}
通常不正确。
经过一番尝试后,感谢@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);
}
我如何确认
当它进入我的代理类时Thread.currentThread().getContextClassLoader()
成为我的应用程序extn加载器。 但是,系统类加载器现在变成了“ ParallelWebappClassLoader”。
我假设这是它的工作方式,但可能会很破旧:
i)当我说Class.forName("my.pkg")
,它将检查现在指向我的加载器的系统类加载器。 如果未找到该类(即未加载),它将归父母所有。我相信这或多或少是委托模型的基础。
ii)这样,通过相同的类加载器将类加载到VM中,在正常情况下,该类加载器还将类加载到我的webapp中。
iii)除了我自己的类之外,我将不会做其他任何事情,因此类加载器将始终相同。
到目前为止,我还没有看到任何LinkageError发生。 但是我仍然觉得这太冒险了,如果我断开链接,那我就搞砸了。
必须避免在Java探查器中使用Class.forName以避免NoClassDef错误。 JVM根据类文件的类路径设置和类文件需求在不同级别的类加载器中加载类文件。
Java Cre库+引导路径保护的库将在引导程序级别加载
Java代理将被加载到系统级别并继续运行。 Class.forName()将在父加载器中查找类文件,当前加载器将不检查子加载器(直到我们实现自己的加载器为止)
您可以从您的应用程序代码访问Java核心类,但是Java核心类将无法访问我们的应用程序代码。 它称为类加载器层次结构。
您有三个选择。
正确维护层次结构,避免出现太多奇怪的错误。
假设您正在寻找Class<?>
以重新转换 class,在我看来,您可以保存传递给转换器的ClassLoader
,然后使用ClassLoader.loadClass(String)
。 就像是:
class MyTransformer implements ClassFileTransformer {
Map<String, ClassLoader> _name2loader = new ...;
...
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd,
byte[] classfileBuffer) throws ... {
...
_name2loader.put(className.replace("/","."), classLoader);
...
}
...
Class<?> getClass(String name) throws ClassNotFoundException {
ClassLoader cl = _name2loader.get(name);
if (cl == null) {
throw ClassNotFoundException("No loader for class " + name);
}
return cl.loadClass(name);
}
}
请注意,传递给transform
的className
使用斜杠,而不是点...比String.replace
更好的替代方法可能是使用您的字节码库(例如 javaassist classfileBuffer
ASM ,但如果您是转换字节码,您可能已经在使用这样的库)。
注意:我不确定您是否会看到相同的 class 被传递以使用不同的ClassLoader
进行转换,但最好注意这一点(或研究它)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.