使用Java代理将类添加到类路径
我正在使用Java Agent和Javassist向某些JDK类添加一些日志记录。本质上,当系统加载一些TLS类时,Javassist将向它们添加一些附加的字节码来帮助我调试一些连接问题。
I'm using a Java Agent and Javassist to add some logging to some JDK classes. Essentially when the system loads some TLS classes, Javassist will add some additional bytecode to them to help me debug some connection problems.
这是问题所在,因为该类已包含在其中代理jar:
Here's the problem, given this class is included in the agent jar:
package com.something.myagent;
public class MyAgentPrinter {
public static final void sayHello() {
System.out.println("Hello!");
}
}
在我的代理的transform方法中,假设我尝试使用javassist调用该类:
In my agent's transform method, let's say I tried to invoke that class using 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();
您认为这行得通,但我却得到了:
You think that would work, but instead I get this:
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
]添加到应用程序的类路径中?
Is there any way to add that class [MyAgentPrinter
] to the application's class path?
您的Agent的jar文件已添加到类路径中,如 java.lang.instrument
软件包文档:
Your Agent’s jar file is already added to the class path, as specified by the java.lang.instrument
package documentation:
代理类将由系统类加载器加载(请参见
ClassLoader.getSystemClassLoader
)。这是类加载器,通常加载包含应用程序main
方法的类。premain
方法将与应用程序main
方法在相同的安全性和类加载器规则下运行。
The agent class will be loaded by the system class loader (see
ClassLoader.getSystemClassLoader
). This is the class loader which typically loads the class containing the applicationmain
method. Thepremain
methods will be run under the same security and classloader rules as the applicationmain
method.
这就是Javassist在转换字节码时可以找到Agent的类的原因。
This is the reason why Javassist can find the Agent’s classes when you are transforming the byte code.
问题似乎是您正在转换 sun。**
类,该类很可能由引导加载程序或扩展加载程序加载。标准的类加载委托是应用程序加载器→扩展加载器→引导加载程序
,因此应用程序加载器可用的类不适用于扩展程序或引导加载程序。
The problem seems to be that you are transforming a sun.**
class which is likely loaded by the bootstrap loader or extension loader. The standard class loading delegation isapplication loader → extension loader → bootstrap loader
, so classes available to the application loader are not available to classes loaded by the extension or bootstrap loader.
因此,要使它们可用于所有类,必须将代理程序的类添加到引导加载程序:
So, to make them available to all classes, you have to add the Agent’s classes to the 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
以避免意外的提前加载。
It’s critical to do this before any other action, i.e. before the classes you want to make available to instrumented code have been loaded. This implies that the Agent class itself, i.e. the class containing the premain
method can not get accessed by the instrumented code. The Agent class also shouldn’t have direct references to MyAgentPrinter
to avoid unintended early loading.
一种更可靠的方法要将 Boot-Class-Path
条目添加到代理jar的清单中,请参见软件包文档的清单属性部分,以便在代理启动之前添加条目。但是,然后jar文件的名称一定不能更改。
A more reliable way is to add a Boot-Class-Path
entry to the Agent jar’s manifest, see the "Manifest Attributes" section of the package documentation, so that the entry gets added before the Agent starts. But then, the name of the jar file must not change afterwards.