You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何借助ASM或Javassist从JAR获取Java方法的所有引用类?

从JAR文件中提取指定方法的所有引用类

一、使用ASM实现

ASM通过直接解析字节码指令和属性提取引用类,核心是自定义访问器遍历类与方法的字节码结构:

  1. 遍历JAR中的目标类:用JarFile扫描JAR内的.class文件,定位到目标类(如示例中的Main.class)。
  2. 自定义ClassVisitor:继承ClassVisitor,在visitMethod中筛选目标方法,关联自定义MethodVisitor。
  3. 自定义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,无需直接操作字节码指令:

  1. 加载JAR到ClassPool:将目标JAR添加到ClassPool,确保能访问目标类。
  2. 获取目标CtClass与CtMethod:通过类名和方法信息定位目标方法。
  3. 提取类引用:
    • 遍历局部变量获取类型。
    • 解析字节码指令捕获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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 14:24:52