我以前使用过仪器来解决类似的问题。
免责声明:只有在您可以控制 JVM 并且可以指定它使用 javaagent 运行时,您才能执行以下操作。
代理的实现相当简单,您只需要一个实现带有premain(String, java.lang.Instrumentation)
签名的方法的类。一个例子如下:
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrumentation.ClassFileTransformer;
import java.lang.instrumentation.IllegalClassFormatException;
import java.lang.instrumentation.Instrumentation;
import java.security.ProtectionDomain;
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassTransformer() {
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
// if you want to target specific classes or packages you can filter
// on the class name, just remember that it is the JVM class format string
if(className.startWith("com/my/package/SomeThing")) {
// javassist provides methods to access classes and generate bytecode
ClassPool cp = ClassPool.getDefault();
// you can access the class with the following
CtClass cc = cp.get("com.my.package.MyClass$InnerClass");
// access specific methods with
CtMethod m = cc.getDeclaredMethod("someMethod");
m.setBody("{MyCallTree tree = com.my.package.TreeSingleton.getTree();\ntree.addCall(" + m.getLongName + ");\n}");
return cc.toByteCode();
}
else {
// return null so that you don't break methods or classes you
// don't want to
return null;
}
});
}
}
在上面的代码片段中,我将整个方法体替换为作为字符串传递给CtMethod.setBody
. 但是,您几乎可以使用 javassist 执行任何操作、前置代码、附加代码等。有关使用 javassist 的详细教程可以在此处找到。这是一个非常强大的库。
您如何实现代码以构建调用信息的细节实际上只是 Java 代码,可能首先将代码编写为项目的一部分,然后只使用在premain
方法中执行步骤的位。您甚至可以让它编写tsolakp在他的回答中建议的代码。
下一步是将上述代码打包到一个 jar 文件中,然后在启动时将其注入到您的 JVM 中,如本答案中所做的那样。