Find java class dependencies at runtime

前端 未结 2 1544
不思量自难忘°
不思量自难忘° 2020-12-18 15:11

What\'s the most effective way to get a list of dependencies for a Java class at runtime?

Using this (based on ASM ByteCode Manipulator 3.3.1), I can do the followin

2条回答
  •  渐次进展
    2020-12-18 16:04

    Holger's answer seems to work well, but I also found the code a bit cryptic. I did a little research and re-worked Holger's code for clarity and readability. I added a main() as an example, which filters out java.lang dependencies (which seem like clutter to me) and prints the full class names.

    public class DependencyFinder {
    
    public static void main(String[] args) {
        try {
            // Get dependencies for my class:
            Set> dependencies = getDependencies(Class
                    .forName("com.example.MyClass"));  // REPLACE WITH YOUR CLASS NAME
    
            // Print the full class name for each interesting dependency:
            dependencies
                    .stream()
                    .filter(clazz -> !clazz.getCanonicalName().startsWith(
                            "java.lang")) // do not show java.lang dependencies,
                                            // which add clutter
                    .forEach(c -> System.out.println(c.getCanonicalName()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * Get the set of direct dependencies for the given class
     * 
     * @param classToCheck
     * @return The direct dependencies for classToCheck, as a set of classes
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Set> getDependencies(final Class classToCheck)
            throws IOException, ClassNotFoundException {
        Class adjustedClassToCheck = adjustSourceClassIfArray(classToCheck);
        if (adjustedClassToCheck.isPrimitive()) {
            return Collections.emptySet();
        }
        return mapClassNamesToClasses(
                adjustedClassToCheck,
                getDependenciesFromClassBytes(readClassBytes(adjustedClassToCheck)));
    }
    
    private static Class adjustSourceClassIfArray(final Class sourceClass) {
        Class adjustedSourceClass = sourceClass;
        while (adjustedSourceClass.isArray()) {
            adjustedSourceClass = sourceClass.getComponentType();
        }
        return adjustedSourceClass;
    }
    
    private static Set> mapClassNamesToClasses(Class from,
            Set names) throws ClassNotFoundException {
        ClassLoader cl = from.getClassLoader();
        Set> classes = new HashSet<>(names.size());
    
        for (String name : names) {
            classes.add(Class.forName(name, false, cl));
        }
        classes.remove(from);// remove self-reference
        return classes;
    }
    
    private static ByteBuffer readClassBytes(Class from) throws IOException {
        Buffer readBuf = new Buffer();
        try (InputStream is = from.getResourceAsStream(from.getSimpleName()
                + ".class")) {
            int byteCountFromLastRead = 0;
            do {
                readBuf.read += byteCountFromLastRead;
                adustBufferSize(readBuf, is);
                byteCountFromLastRead = is.read(readBuf.buf, readBuf.read,
                        readBuf.buf.length - readBuf.read);
            } while (byteCountFromLastRead > 0);
        }
        return readBuf.toByteBuffer();
    }
    
    private static void adustBufferSize(Buffer readBuf, InputStream is)
            throws IOException {
        int bufferSize = Math.max(is.available() + 100, 100);
        if (readBuf.buf == null) {
            readBuf.buf = new byte[bufferSize];
        } else if (readBuf.buf.length - readBuf.read < bufferSize) {
            System.arraycopy(readBuf.buf, 0,
                    readBuf.buf = new byte[readBuf.read + bufferSize], 0,
                    readBuf.read);
        }
    }
    
    private static Set getDependenciesFromClassBytes(
            ByteBuffer readBuffer) {
        verifyMagicFileTypeHeader(readBuffer);
        final int constantPoolItemCount = getConstantPoolItemCount(readBuffer);
        ConstantPoolItemFlags flags = new ConstantPoolItemFlags(constantPoolItemCount);
        flagConstantPoolItemsAsDependencies(readBuffer, constantPoolItemCount, flags);
        return extractClassNamesFromConstantsBasedOnFlags(readBuffer,
                constantPoolItemCount, flags);
    }
    
    private static void flagConstantPoolItemsAsDependencies(ByteBuffer readBuffer,
            final int constantPoolItemCount, ConstantPoolItemFlags flags) {
        for (int c = 1; c < constantPoolItemCount; c++) {
            c = readOneConstantPoolItemAndSetFlagIfClassOrNamedType(readBuffer,
                    flags, c);
        }
        skipPastAccessFlagsThisClassAndSuperClass(readBuffer);
        skipInterfaces(readBuffer);
        flagFieldsAndMethodsAsNamedTypes(readBuffer, flags.isNamedType);
    }
    
    private static int getConstantPoolItemCount(ByteBuffer readBuffer) {
        setCursorToConstantPoolCountPosition(readBuffer);
        final int constantPoolCount = readBuffer.getChar();
        return constantPoolCount;
    }
    
    /**
     * @param readBuffer
     */
    private static void skipInterfaces(ByteBuffer readBuffer) {
        readBuffer.position(readBuffer.getChar() * 2 + readBuffer.position());
    }
    
    /**
     * @param readBuffer
     */
    private static void skipPastAccessFlagsThisClassAndSuperClass(
            ByteBuffer readBuffer) {
        skipBytes(readBuffer, 6);
    }
    
    /**
     * @param readBuffer
     * @param numberOfConstants
     * @param isClass
     * @param isNamedType
     * @return
     * @throws AssertionError
     */
    private static HashSet extractClassNamesFromConstantsBasedOnFlags(
            ByteBuffer readBuffer, final int numberOfConstants, ConstantPoolItemFlags flags) throws AssertionError {
        HashSet names = new HashSet<>();
        returnBufferToStartOfConstantPool(readBuffer);
        for (int constantPoolIndex = 1; constantPoolIndex < numberOfConstants; constantPoolIndex++) {
            switch (readBuffer.get()) {
            case CONSTANT_Utf8:
                readClassNamesInUTF8Value(readBuffer, flags,
                        names, constantPoolIndex);
                break;
            case CONSTANT_Integer:
            case CONSTANT_Float:
            case CONSTANT_FieldRef:
            case CONSTANT_MethodRef:
            case CONSTANT_InterfaceMethodRef:
            case CONSTANT_NameAndType:
            case CONSTANT_InvokeDynamic:
                skipBytes(readBuffer, 4);
                break;
            case CONSTANT_Long:
            case CONSTANT_Double:
                skipBytes(readBuffer, 8);
                constantPoolIndex++; // long or double counts as 2 items
                break;
            case CONSTANT_String:
            case CONSTANT_Class:
            case CONSTANT_MethodType:
                skipBytes(readBuffer, 2);
                break;
            case CONSTANT_MethodHandle:
                skipBytes(readBuffer, 3);
                break;
            default:
                throw new AssertionError();
            }
        }
        return names;
    }
    
    /**
     * @param readBuffer
     * @param isClass
     * @param isNamedType
     * @param dependencyClassNames
     * @param constantNumber
     */
    private static void readClassNamesInUTF8Value(ByteBuffer readBuffer,
            ConstantPoolItemFlags flags,
            HashSet dependencyClassNames, int constantNumber) {
        int strSize = readBuffer.getChar(), strStart = readBuffer.position();
        boolean multipleNames = flags.isNamedType.get(constantNumber);
        if (flags.isClass.get(constantNumber)) {
            if (readBuffer.get(readBuffer.position()) == ARRAY_START_CHAR) {
                multipleNames = true;
            } else {
                addClassNameToDependencySet(dependencyClassNames, readBuffer,
                        strStart, strSize);
            }
        }
        if (multipleNames) {
            addClassNamesToDependencySet(dependencyClassNames, readBuffer,
                    strStart, strSize);
        }
        readBuffer.position(strStart + strSize);
    }
    
    /**
     * @param readBuffer
     * @param isNamedType
     */
    private static void flagFieldsAndMethodsAsNamedTypes(ByteBuffer readBuffer,
            BitSet isNamedType) {
        for (int type = 0; type < 2; type++) { // fields and methods
            int numMember = readBuffer.getChar();
            for (int member = 0; member < numMember; member++) {
                skipBytes(readBuffer, 4);
                isNamedType.set(readBuffer.getChar());
                int numAttr = readBuffer.getChar();
                for (int attr = 0; attr < numAttr; attr++) {
                    skipBytes(readBuffer, 2);
                    readBuffer.position(readBuffer.getInt()
                            + readBuffer.position());
                }
            }
        }
    }
    
    /**
     * @param readBuffer
     */
    private static void returnBufferToStartOfConstantPool(ByteBuffer readBuffer) {
        readBuffer.position(10);
    }
    
    /**
     * @param readBuffer
     * @param isClass
     * @param isNamedType
     * @param currentConstantIndex
     * @return
     */
    private static int readOneConstantPoolItemAndSetFlagIfClassOrNamedType(
            ByteBuffer readBuffer, ConstantPoolItemFlags flags,
            int currentConstantIndex) {
        switch (readBuffer.get()) {
        case CONSTANT_Utf8:
            skipPastVariableLengthString(readBuffer);
            break;
        case CONSTANT_Integer:
        case CONSTANT_Float:
        case CONSTANT_FieldRef:
        case CONSTANT_MethodRef:
        case CONSTANT_InterfaceMethodRef:
        case CONSTANT_InvokeDynamic:
            skipBytes(readBuffer, 4);
            break;
        case CONSTANT_Long:
        case CONSTANT_Double:
            skipBytes(readBuffer, 8);
            currentConstantIndex++;
            break;
        case CONSTANT_String:
            skipBytes(readBuffer, 2);
            break;
        case CONSTANT_NameAndType:
            skipBytes(readBuffer, 2);// skip name, fall through to flag as a
                                        // named type:
        case CONSTANT_MethodType:
            flags.isNamedType.set(readBuffer.getChar()); // flag as named type
            break;
        case CONSTANT_Class:
            flags.isClass.set(readBuffer.getChar()); // flag as class
            break;
        case CONSTANT_MethodHandle:
            skipBytes(readBuffer, 3);
            break;
        default:
            throw new IllegalArgumentException("constant pool item type "
                    + (readBuffer.get(readBuffer.position() - 1) & 0xff));
        }
        return currentConstantIndex;
    }
    
    private static void skipBytes(ByteBuffer readBuffer, int bytesToSkip) {
        readBuffer.position(readBuffer.position() + bytesToSkip);
    }
    
    private static void skipPastVariableLengthString(ByteBuffer readBuffer) {
        readBuffer.position(readBuffer.getChar() + readBuffer.position());
    }
    
    private static void setCursorToConstantPoolCountPosition(
            ByteBuffer readBuffer) {
        readBuffer.position(8);
    }
    
    private static void verifyMagicFileTypeHeader(ByteBuffer readBuffer) {
        if (readBuffer.getInt() != 0xcafebabe) {
            throw new IllegalArgumentException("Not a class file");
        }
    }
    
    private static void addClassNameToDependencySet(HashSet names,
            ByteBuffer readBuffer, int start, int length) {
        final int end = start + length;
        StringBuilder dst = new StringBuilder(length);
        ascii: {
            for (; start < end; start++) {
                byte b = readBuffer.get(start);
                if (b < 0) {
                    break ascii;
                }
                dst.append((char) (b == '/' ? '.' : b));
            }
            names.add(dst.toString());
            return;
        }
        final int oldLimit = readBuffer.limit(), oldPos = dst.length();
        readBuffer.limit(end).position(start);
        dst.append(StandardCharsets.UTF_8.decode(readBuffer));
        readBuffer.limit(oldLimit);
        for (int pos = oldPos, len = dst.length(); pos < len; pos++) {
            if (dst.charAt(pos) == '/') {
                dst.setCharAt(pos, '.');
            }
        }
        names.add(dst.toString());
        return;
    }
    
    private static void addClassNamesToDependencySet(HashSet names,
            ByteBuffer readBuffer, int start, int length) {
        final int end = start + length;
        for (; start < end; start++) {
            if (readBuffer.get(start) == 'L') {
                int endMarkerPosition = start + 1;
                while (readBuffer.get(endMarkerPosition) != ';') {
                    endMarkerPosition++;
                }
                addClassNameToDependencySet(names, readBuffer, start + 1,
                        calculateLength(start, endMarkerPosition));
                start = endMarkerPosition;
            }
        }
    }
    
    private static int calculateLength(int start, int endMarkerPosition) {
        return endMarkerPosition - start - 1;
    }
    
    private static final char ARRAY_START_CHAR = '[';
    
    // Constant pool data type constants:
    private static final byte CONSTANT_Utf8 = 1, CONSTANT_Integer = 3,
            CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6,
            CONSTANT_Class = 7, CONSTANT_String = 8, CONSTANT_FieldRef = 9,
            CONSTANT_MethodRef = 10, CONSTANT_InterfaceMethodRef = 11,
            CONSTANT_NameAndType = 12, CONSTANT_MethodHandle = 15,
            CONSTANT_MethodType = 16, CONSTANT_InvokeDynamic = 18;
    
    // encapsulate byte buffer with its read count:
    private static class Buffer {
        byte[] buf = null;
        int read = 0;
    
        // convert to ByteBuffer
        ByteBuffer toByteBuffer() {
            return ByteBuffer.wrap(this.buf, 0, this.read);
        }
    }
    
    // flags for identifying dependency names in the constant pool
    private static class ConstantPoolItemFlags {
        final BitSet isClass;
        final BitSet isNamedType;
    
        ConstantPoolItemFlags(int constantPoolItemCount) {
            isClass = new BitSet(constantPoolItemCount);
            isNamedType = new BitSet(constantPoolItemCount);
        }
    }
    

    }

提交回复
热议问题