如何基于ASM查找指定类所有方法的调用者并输出调用关系至文件?
用ASM追踪ExceptionFactory调用链路并生成结果文件
核心逻辑
直接用ASM遍历服务层类的字节码,捕获所有调用ExceptionFactory方法的指令,记录调用方(服务层方法)和被调用方(ExceptionFactory的方法)的对应关系,最后输出到文件。把这套逻辑做成JUnit测试用例,跑测试时自动执行扫描并生成结果。
具体实现步骤
1. 引入ASM依赖
如果用Maven,直接在pom.xml里加测试依赖:
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.5</version> <scope>test</scope> </dependency>
2. 编写ASM追踪器
自定义ClassVisitor和MethodVisitor,用来识别并记录调用关系:
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.io.PrintWriter; import java.util.HashSet; import java.util.Set; public class ExceptionFactoryCallTracker extends ClassVisitor { // 替换成你的ExceptionFactory全类名,注意用/代替. private static final String TARGET_CLASS = "com/yourproject/exception/ExceptionFactory"; private final PrintWriter writer; private String currentClassName; private final Set<String> recordedCalls = new HashSet<>(); // 避免重复记录同一调用 public ExceptionFactoryCallTracker(ClassVisitor cv, PrintWriter writer) { super(Opcodes.ASM9, cv); this.writer = writer; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // 把ASM的类名格式(用/分隔)转成标准Java类名 this.currentClassName = name.replace('/', '.'); super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); // 给每个方法绑定追踪器,记录当前方法的信息 return new MethodCallTracker(mv, currentClassName, name); } private class MethodCallTracker extends MethodVisitor { private final String callerClass; private final String callerMethod; public MethodCallTracker(MethodVisitor mv, String callerClass, String callerMethod) { super(Opcodes.ASM9, mv); this.callerClass = callerClass; this.callerMethod = callerMethod; } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { // 匹配调用ExceptionFactory的指令 if (TARGET_CLASS.equals(owner)) { String targetMethod = owner.replace('/', '.') + "." + name; String callEntry = callerClass + "." + callerMethod + " -> " + targetMethod; // 去重后写入文件 if (recordedCalls.add(callEntry)) { writer.println(callEntry); } } super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } } }
3. 编写类扫描工具
用来遍历指定目录下的所有类文件:
import org.objectweb.asm.ClassReader; import java.io.File; import java.io.FileInputStream; import java.io.PrintWriter; public class ClassScanner { public static void scanClasses(File classDir, PrintWriter writer) throws Exception { if (!classDir.isDirectory()) { return; } File[] files = classDir.listFiles(); if (files == null) { return; } for (File file : files) { if (file.isDirectory()) { // 递归扫描子目录 scanClasses(file, writer); } else if (file.getName().endsWith(".class")) { // 读取类文件并交给ASM处理 try (FileInputStream fis = new FileInputStream(file)) { ClassReader cr = new ClassReader(fis); cr.accept(new ExceptionFactoryCallTracker(null, writer), ClassReader.SKIP_DEBUG); } } } } }
4. 集成到JUnit测试用例
这样跑测试时自动执行扫描,生成结果文件:
import org.junit.jupiter.api.Test; import java.io.File; import java.io.PrintWriter; public class ExceptionCallTrackingTest { @Test public void trackExceptionFactoryCalls() throws Exception { // 替换成你的服务层类编译后的目录,比如target/classes/com/yourproject/service File serviceClassDir = new File("target/classes/com/yourproject/service"); // 输出结果的文件路径 try (PrintWriter writer = new PrintWriter("exception_service_calls.txt")) { ClassScanner.scanClasses(serviceClassDir, writer); } } }
可选优化
- 要是只想追踪ExceptionFactory里生成特定异常的方法,可以在
visitMethodInsn里加方法名或描述符的判断,比如只匹配createUserNotFoundException这类方法 - 如果需要完整调用链路(比如上层控制器调用服务层,服务层调用ExceptionFactory),可以扩展扫描范围到控制器目录,然后递归追踪调用关系,但要注意处理循环引用
- 可以把输出结果按类分组,或者加上方法参数信息,方便更精准定位
内容的提问来源于stack exchange,提问作者pitrisss
相关产品推荐
相关产品推荐

