我正在使用Java Agent和Javassist为某些JDK类添加一些日志记录。本质上,当系统加载一些TLS类时,Javassist会向它们添加一些额外的字节码,以帮助我调试一些连接问题。
这是问题,因为这个类包含在代理jar中:
package com.something.myagent;
public class MyAgentPrinter {
public static final void sayHello() {
System.out.println("Hello!");
}
}
在我的代理的转换方法中,假设我尝试使用javassist调用该类:
// this is only called for sun.security.ssl.Handshaker
ClassPool cp = getClassPool(classfileBuffer, className);
CtClass cc = cp.get(className);
CtMethod declaredMethod = cc.getDeclaredMethod("calculateKeys");
declaredMethod.insertAfter("com.something.myagent.MyAgentPrinter.sayHello();");
cc.freeze();
return cc.toBytecode();
你认为这会奏效,但我得到了这个:
java.lang.NoClassDefFoundError: com/something/myagent/MyAgentPrinter
at sun.security.ssl.Handshaker.printLogLine(Handshaker.java)
at sun.security.ssl.Handshaker.calculateKeys(Handshaker.java:1160)
at sun.security.ssl.ServerHandshaker.processMessage(ServerHandshaker.java:292)
有没有办法将该类[MyAgentPrinter
]添加到应用程序的类路径?
答案 0 :(得分:5)
您的代理的jar文件已添加到类路径中,由the java.lang.instrument
package documentation指定:
代理类将由系统类加载器加载(请参阅
ClassLoader.getSystemClassLoader
)。这是类加载器,它通常加载包含应用程序main
方法的类。premain
方法将在与应用程序main
方法相同的安全性和类加载器规则下运行。
这就是为什么Javassist可以在转换字节代码时找到代理的类。
问题似乎在于您正在转换可能由引导加载程序或扩展加载程序加载的sun.**
类。标准类加载委托是
application loader → extension loader → bootstrap loader
,因此应用程序加载器可用的类对扩展或引导加载程序加载的类不可用。
因此,要使它们可用于所有类,您必须将代理的类添加到引导加载程序中:
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) throws IOException {
JarURLConnection connection = (JarURLConnection)
MyAgent.class.getResource("MyAgent.class").openConnection();
inst.appendToBootstrapClassLoaderSearch(connection.getJarFile());
// proceed
}
}
在任何其他操作之前执行此操作至关重要,即在您要为已检测代码提供的类之前已加载。这意味着Agent类本身,即包含premain
方法的类不能被检测代码访问。 Agent类也不应该直接引用MyAgentPrinter
以避免意外的早期加载。
更可靠的方法是在Agent jar的清单中添加Boot-Class-Path
条目,请参阅the “Manifest Attributes” section of the package documentation,以便在代理启动之前添加条目。但是,之后,jar文件的名称不得更改。