我不知道 Spring 是否有板载方法可以做到这一点,但 AFAIK 嵌入在 Spring 中的 ASM 类不包括 TraceClassVisitor。因此,如果您喜欢它的输出,则必须直接使用 ASM(工件asm-util),我假设您为示例代码所做的。如果您想坚持使用板载方式,您可以编写一个转换器,将完整的字节码作为类文件转储到一个文件中,然后使用 JDK 命令行工具javap 查看该文件,例如通过javap -c -p -v MyDumpedBytes.class。
无论如何,一个简单的事情是实现一个 Java 代理并将其附加到 Java 命令行,通过 -javaagent:/path/to/my-agent.jar 启动 Spring 项目。我为您找到了this article,它解释了如何使用清单文件等实现简单的 Java 代理,以及如何通过 Java 附加 API 将其附加到正在运行的进程。本文使用 Javassist 作为编写转换器的示例,但您可以直接使用 ASM。
您的 Java 代理 + 转换器将如下所示:
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
class TraceClassTransformer implements ClassFileTransformer {
/**
* Attach agent dynamically after JVM start-up
*/
public static void agentmain(String commandLineOptions, Instrumentation instr) {
premain(commandLineOptions, instr);
}
/**
* Start agent via <code>-javaagent:/path/to/my-agent.jar=<i>options</i></code> JVM parameter
*/
public static void premain(String commandLineOptions, Instrumentation instrumentation) {
TraceClassTransformer transformer = new TraceClassTransformer();
instrumentation.addTransformer(transformer, true);
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
dumpClass(classfileBuffer);
// Do not apply any transformation
return null;
}
private void dumpClass(byte[] classfileBuffer) {
TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
ClassReader reader = new ClassReader(classfileBuffer);
reader.accept(visitor, 0);
}
}
只需确保代理的清单通过Can-Retransform-Classes: true 启用重新转换即可。
代理启动后,您只需拨打instrumentation.retransformClasses(proxyInstance.getClass());即可享受日志输出。
为了让这个例子更简单,让我们使用byte-buddy-agent,它包含一个简洁的小工具集,可以在运行时附加转换器,而不需要将它们包装到Java代理中。工件很小,不包含 ByteBuddy 的其余部分,只是代理工具。
这会将您的类简化为(您可以保留或删除 premain 和 agentmain 方法,具体取决于您是否计划将该类用作 Java 代理):
import net.bytebuddy.agent.ByteBuddyAgent;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.Closeable;
import java.io.PrintWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Proxy;
import java.security.ProtectionDomain;
class TraceClassTransformer implements ClassFileTransformer {
public static void main(String[] args) throws UnmodifiableClassException {
// Easy way to get an Instrumentation instance, so we can directly register our transformer on it
Instrumentation instrumentation = ByteBuddyAgent.install();
// I am just creating a Java dynamic proxy for a JRE interface. In your own application,
// you would just get a reference to a dynamic proxy created by Spring.
Object proxyInstance = Proxy.newProxyInstance(
Closeable.class.getClassLoader(),
new Class<?>[] { Closeable.class },
(proxy, method, args1) -> null
);
// Register + use dummy ClassFileTransformer, then unregister again (optional)
TraceClassTransformer transformer = new TraceClassTransformer();
try {
instrumentation.addTransformer(transformer, true);
instrumentation.retransformClasses(proxyInstance.getClass());
}
finally {
instrumentation.removeTransformer(transformer);
}
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
dumpClass(classfileBuffer);
// Do not apply any transformation
return null;
}
private void dumpClass(byte[] classfileBuffer) {
TraceClassVisitor visitor = new TraceClassVisitor(new PrintWriter(System.out));
ClassReader reader = new ClassReader(classfileBuffer);
reader.accept(visitor, 0);
}
}
在运行示例 main 类时,您会得到如下控制台日志:
// class version 58.0 (58)
// access flags 0x11
public final class com/sun/proxy/$Proxy0 extends java/lang/reflect/Proxy implements java/io/Closeable {
// access flags 0xA
private static Ljava/lang/reflect/Method; m0
// access flags 0xA
private static Ljava/lang/reflect/Method; m1
// access flags 0xA
private static Ljava/lang/reflect/Method; m2
// access flags 0xA
private static Ljava/lang/reflect/Method; m3
// access flags 0x1
public <init>(Ljava/lang/reflect/InvocationHandler;)V
ALOAD 0
ALOAD 1
INVOKESPECIAL java/lang/reflect/Proxy.<init> (Ljava/lang/reflect/InvocationHandler;)V
RETURN
MAXSTACK = 2
MAXLOCALS = 2
(...)