You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何通过Java从.class文件获取字节码依赖信息并分析类引用关系?

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

火山引擎 最新活动