如何通过Java从.class文件获取字节码依赖信息并分析类引用关系?
Great question! Ditching external tools like jdeps and building this logic directly in Java is totally feasible. Let's break down how to tackle both your requirements using a robust bytecode manipulation library called ASM—it's far easier than manually parsing the complex .class file format from scratch.
1. 从.class文件获取字节码(依赖)基础信息
ASM provides a straightforward way to read and inspect .class files without executing them. Here's a step-by-step approach:
Step 1: Add ASM Dependency
First, include the ASM core library in your project. If you're using Maven, add this to your pom.xml:
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>9.5</version> </dependency>
Step 2: Implement a ClassVisitor to Extract Basic Info
Create a custom ClassVisitor that captures key bytecode details when parsing the .class file:
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; public class BasicClassInfoVisitor extends ClassVisitor { private String className; private String superClassName; private String[] interfaceNames; private int classVersion; public BasicClassInfoVisitor() { super(Opcodes.ASM9); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.classVersion = version; this.className = convertInternalNameToFullyQualified(name); this.superClassName = convertInternalNameToFullyQualified(superName); this.interfaceNames = new String[interfaces.length]; for (int i = 0; i < interfaces.length; i++) { interfaceNames[i] = convertInternalNameToFullyQualified(interfaces[i]); } super.visit(version, access, name, signature, superName, interfaces); } // Helper to convert ASM's internal name (e.g., "java/lang/String") to fully qualified name private String convertInternalNameToFullyQualified(String internalName) { return internalName != null ? internalName.replace('/', '.') : null; } // Getters for the extracted info public String getClassName() { return className; } public String getSuperClassName() { return superClassName; } public String[] getInterfaceNames() { return interfaceNames; } public int getClassVersion() { return classVersion; } }
Step 3: Read the .class File
Use ClassReader to feed the .class file into your visitor:
import org.objectweb.asm.ClassReader; import java.io.FileInputStream; import java.io.IOException; public class ClassInfoExtractor { public static void main(String[] args) throws IOException { String classFilePath = "path/to/YourClass.class"; try (FileInputStream fis = new FileInputStream(classFilePath)) { ClassReader classReader = new ClassReader(fis); BasicClassInfoVisitor visitor = new BasicClassInfoVisitor(); classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); // Print the extracted info System.out.println("Class Name: " + visitor.getClassName()); System.out.println("Super Class: " + visitor.getSuperClassName()); System.out.println("Interfaces: " + String.join(", ", visitor.getInterfaceNames())); System.out.println("Java Version: " + mapClassVersionToJavaVersion(visitor.getClassVersion())); } } // Map ASM's class version to human-readable Java version private static String mapClassVersionToJavaVersion(int version) { return switch (version) { case Opcodes.V1_8 -> "Java 8"; case Opcodes.V11 -> "Java 11"; case Opcodes.V17 -> "Java 17"; // Add more versions as needed default -> "Unknown (version: " + version + ")"; }; } }
2. 分析.class文件获取类引用关系
To track which classes are referenced by a target .class file, extend the ASM visitor to capture all class references across fields, methods, constants, and more.
Step 1: Create a Dependency-Tracking Visitor
This visitor will collect all unique class references:
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.HashSet; import java.util.Set; public class DependencyTrackingVisitor extends ClassVisitor { private final Set<String> referencedClasses = new HashSet<>(); private String currentClassName; public DependencyTrackingVisitor() { super(Opcodes.ASM9); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.currentClassName = convertInternalNameToFullyQualified(name); // Add super class and interfaces as dependencies addIfNotSelf(superName); for (String iface : interfaces) { addIfNotSelf(iface); } super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitField(int access, String name, String descriptor, String signature, Object value) { // Extract class references from field type descriptor extractClassesFromDescriptor(descriptor); super.visitField(access, name, descriptor, signature, value); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { // Extract class references from method descriptor (params, return type) extractClassesFromDescriptor(descriptor); // Add exception types as dependencies if (exceptions != null) { for (String exception : exceptions) { addIfNotSelf(exception); } } return new MethodDependencyVisitor(super.visitMethod(access, name, descriptor, signature, exceptions)); } @Override public void visitLdcInsn(Object value) { // Capture class constants (e.g., String.class, Integer.class) if (value instanceof org.objectweb.asm.Type type) { if (type.getSort() == org.objectweb.asm.Type.OBJECT) { addIfNotSelf(type.getInternalName()); } } super.visitLdcInsn(value); } // Helper to extract class names from type descriptors (e.g., "(Ljava/lang/String;)V") private void extractClassesFromDescriptor(String descriptor) { org.objectweb.asm.Type[] types = org.objectweb.asm.Type.getArgumentTypes(descriptor); for (org.objectweb.asm.Type type : types) { if (type.getSort() == org.objectweb.asm.Type.OBJECT) { addIfNotSelf(type.getInternalName()); } else if (type.getSort() == org.objectweb.asm.Type.ARRAY) { // Handle array types (e.g., [Ljava/lang/String; -> java.lang.String) org.objectweb.asm.Type componentType = type.getElementType(); if (componentType.getSort() == org.objectweb.asm.Type.OBJECT) { addIfNotSelf(componentType.getInternalName()); } } } org.objectweb.asm.Type returnType = org.objectweb.asm.Type.getReturnType(descriptor); if (returnType.getSort() == org.objectweb.asm.Type.OBJECT) { addIfNotSelf(returnType.getInternalName()); } else if (returnType.getSort() == org.objectweb.asm.Type.ARRAY) { org.objectweb.asm.Type componentType = returnType.getElementType(); if (componentType.getSort() == org.objectweb.asm.Type.OBJECT) { addIfNotSelf(componentType.getInternalName()); } } } // Avoid adding the class itself as a dependency private void addIfNotSelf(String internalName) { if (internalName == null) return; String fullyQualified = convertInternalNameToFullyQualified(internalName); if (!fullyQualified.equals(currentClassName)) { referencedClasses.add(fullyQualified); } } private String convertInternalNameToFullyQualified(String internalName) { return internalName.replace('/', '.'); } public Set<String> getReferencedClasses() { return referencedClasses; } // Inner class to track method instructions (e.g., invocations, new instances) private class MethodDependencyVisitor extends MethodVisitor { public MethodDependencyVisitor(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitTypeInsn(int opcode, String type) { // Capture new class instances (NEW opcode) or type checks (INSTANCEOF, CHECKCAST) addIfNotSelf(type); super.visitTypeInsn(opcode, type); } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { // Capture method calls to other classes addIfNotSelf(owner); super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } @Override public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { // Capture invokedynamic targets (e.g., lambdas) extractClassesFromDescriptor(descriptor); super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); } } }
Step 2: Extract Dependencies from a .class File
Use this visitor to collect all referenced classes:
import org.objectweb.asm.ClassReader; import java.io.FileInputStream; import java.io.IOException; import java.util.Set; public class DependencyAnalyzer { public static void main(String[] args) throws IOException { String classFilePath = "path/to/YourClass.class"; try (FileInputStream fis = new FileInputStream(classFilePath)) { ClassReader classReader = new ClassReader(fis); DependencyTrackingVisitor visitor = new DependencyTrackingVisitor(); classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); System.out.println("Class: " + visitor.getReferencedClasses().stream().findFirst().orElse("Unknown")); System.out.println("Referenced Classes:"); for (String clazz : visitor.getReferencedClasses()) { // Optional: Filter out JDK classes if needed if (!clazz.startsWith("java.")) { System.out.println("- " + clazz); } } } } }
Bonus: Analyze Entire JAR Files
To scan all .class files in a JAR, add logic to iterate over JAR entries:
import java.io.IOException; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class JarDependencyAnalyzer { public static void main(String[] args) throws IOException { String jarPath = "path/to/your.jar"; try (JarFile jarFile = new JarFile(jarPath)) { Enumeration<JarEntry> entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (entry.getName().endsWith(".class") && !entry.isDirectory()) { ClassReader classReader = new ClassReader(jarFile.getInputStream(entry)); DependencyTrackingVisitor visitor = new DependencyTrackingVisitor(); classReader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); System.out.println("\n" + visitor.getReferencedClasses().stream().findFirst().orElse(entry.getName())); System.out.println("Dependencies:"); visitor.getReferencedClasses().forEach(clazz -> System.out.println("- " + clazz)); } } } } }
内容的提问来源于stack exchange,提问作者JF Meier




