如何借助ASM或Javassist从JAR获取Java方法的所有引用类?
从JAR文件中提取指定方法的所有引用类
一、使用ASM实现
ASM通过直接解析字节码指令和属性提取引用类,核心是自定义访问器遍历类与方法的字节码结构:
- 遍历JAR中的目标类:用
JarFile扫描JAR内的.class文件,定位到目标类(如示例中的Main.class)。 - 自定义ClassVisitor:继承
ClassVisitor,在visitMethod中筛选目标方法,关联自定义MethodVisitor。 - 自定义MethodVisitor:重写指令处理方法捕获类引用:
visitTypeInsn:处理NEW、CHECKCAST等指令,提取类名。visitLdcInsn:捕获类常量引用(如String.class)。visitLocalVariable:提取局部变量的类型信息。- 解析
SignatureAttribute:获取泛型参数类型(如List<String>中的String)。
ASM代码示例
import org.objectweb.asm.*; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import java.io.*; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class ASMClassReferenceExtractor { private static final Set<String> referencedClasses = new HashSet<>(); public static Set<String> extractFromJar(String jarPath, String targetClassName, String targetMethodName, String targetMethodDesc) throws IOException { referencedClasses.clear(); try (JarFile jarFile = new JarFile(jarPath)) { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); String entryClassName = entry.getName().replace('/', '.'); if (entry.getName().endsWith(".class") && entryClassName.equals(targetClassName + ".class")) { try (InputStream is = jarFile.getInputStream(entry)) { ClassReader cr = new ClassReader(is); cr.accept(new ClassVisitor(Opcodes.ASM9) { @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if (name.equals(targetMethodName) && descriptor.equals(targetMethodDesc)) { // 解析方法泛型签名 if (signature != null) { new SignatureReader(signature).accept(new SignatureVisitor(Opcodes.ASM9) { @Override public SignatureVisitor visitTypeArgument(char wildcard) { return new SignatureVisitor(Opcodes.ASM9) { @Override public void visitClassType(String name) { referencedClasses.add(name.replace('/', '.')); } }; } }); } return new MethodVisitor(Opcodes.ASM9) { @Override public void visitTypeInsn(int opcode, String type) { if (opcode == Opcodes.NEW || opcode == Opcodes.CHECKCAST || opcode == Opcodes.ANEWARRAY) { referencedClasses.add(type.replace('/', '.')); } super.visitTypeInsn(opcode, type); } @Override public void visitLdcInsn(Object cst) { if (cst instanceof Type) { Type type = (Type) cst; if (type.getSort() == Type.OBJECT) { referencedClasses.add(type.getClassName()); } } super.visitLdcInsn(cst); } @Override public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { Type type = Type.getType(descriptor); if (type.getSort() == Type.OBJECT) { referencedClasses.add(type.getClassName()); } // 解析局部变量泛型签名 if (signature != null) { new SignatureReader(signature).accept(new SignatureVisitor(Opcodes.ASM9) { @Override public void visitClassType(String name) { referencedClasses.add(name.replace('/', '.')); } }); } super.visitLocalVariable(name, descriptor, signature, start, end, index); } }; } return super.visitMethod(access, name, descriptor, signature, exceptions); } }, 0); } } } } return referencedClasses; } public static void main(String[] args) throws IOException { Set<String> classes = extractFromJar("your-target.jar", "Main", "main", "([Ljava/lang/String;)V"); classes.forEach(System.out::println); } }
二、使用Javassist实现
Javassist提供贴近Java语法的API,无需直接操作字节码指令:
- 加载JAR到ClassPool:将目标JAR添加到
ClassPool,确保能访问目标类。 - 获取目标CtClass与CtMethod:通过类名和方法信息定位目标方法。
- 提取类引用:
- 遍历局部变量获取类型。
- 解析字节码指令捕获
NEW实例化的类。 - 解析
SignatureAttribute提取泛型参数。
Javassist代码示例
import javassist.*; import javassist.bytecode.*; import javassist.bytecode.analysis.Type; import java.io.IOException; import java.util.HashSet; import java.util.Set; public class JavassistClassReferenceExtractor { public static Set<String> extractFromJar(String jarPath, String targetClassName, String targetMethodName, String targetMethodDesc) throws NotFoundException, IOException { Set<String> referencedClasses = new HashSet<>(); ClassPool pool = ClassPool.getDefault(); pool.appendClassPath(new JarClassPath(jarPath)); CtClass ctClass = pool.get(targetClassName); CtMethod ctMethod = ctClass.getMethod(targetMethodName, targetMethodDesc); // 提取局部变量类型 LocalVariableAttribute lva = (LocalVariableAttribute) ctMethod.getMethodInfo().getCodeAttribute().getAttribute(LocalVariableAttribute.tag); if (lva != null) { int maxLocals = ctMethod.getMethodInfo().getCodeAttribute().getMaxLocals(); for (int i = 0; i < maxLocals; i++) { String typeDesc = lva.descriptor(i); Type type = Type.getType(typeDesc); if (type.getSort() == Type.OBJECT) { referencedClasses.add(type.getClassName()); } } } // 捕获NEW指令实例化的类 CodeIterator iterator = ctMethod.getMethodInfo().getCodeAttribute().iterator(); while (iterator.hasNext()) { int index = iterator.next(); int opcode = iterator.byteAt(index); if (opcode == Opcode.NEW) { int classIndex = iterator.u16bitAt(index + 1); ConstPool cp = ctMethod.getMethodInfo().getConstPool(); String className = cp.getClassInfo(classIndex); referencedClasses.add(className.replace('/', '.')); } } // 解析泛型签名 String methodSignature = ctMethod.getSignature(); if (methodSignature != null) { SignatureAttribute sig = new SignatureAttribute(ctMethod.getMethodInfo().getConstPool(), methodSignature); // 处理泛型参数 SignatureAttribute.Type[] paramTypes = sig.getParameterTypes(); for (SignatureAttribute.Type pt : paramTypes) { if (pt instanceof SignatureAttribute.ClassType) { addGenericTypes((SignatureAttribute.ClassType) pt, referencedClasses); } } } ctClass.detach(); return referencedClasses; } private static void addGenericTypes(SignatureAttribute.ClassType classType, Set<String> classes) { classes.add(classType.getName()); SignatureAttribute.Type[] args = classType.getTypeArguments(); if (args != null) { for (SignatureAttribute.Type arg : args) { if (arg instanceof SignatureAttribute.ClassType) { addGenericTypes((SignatureAttribute.ClassType) arg, classes); } } } } public static void main(String[] args) throws Exception { Set<String> classes = extractFromJar("your-target.jar", "Main", "main", "([Ljava/lang/String;)V"); classes.forEach(System.out::println); } }
注意事项
- 方法描述符:需传入正确的方法描述符(如
main方法的描述符为([Ljava/lang/String;)V),可通过ASM的Type.getMethodDescriptor生成。 - 泛型处理:Java泛型擦除后仅签名属性保留泛型信息,必须解析
SignatureAttribute才能获取泛型参数类。 - 依赖访问:若目标类依赖外部JAR,需确保ASM/Javassist能访问到这些依赖类,避免解析失败。
内容的提问来源于stack exchange,提问作者buriedpot
相关产品推荐
相关产品推荐

