3

在运行时获取 Java 类的依赖项列表的最有效方法是什么?

使用(基于 ASM ByteCode Manipulator 3.3.1),我可以执行以下操作:

final Collection<Class<?>> classes = 
  getClassesUsedBy(MyService.class.getName(), "com");

这将返回对BasicServiceand的引用IService,但错过ContainerValue了 ,这就是问题所在。我玩弄了 ASM 代码,但不知道如何获取 ContainerValue。

package com.foo.bar;

    public class MyService extends BasicService implements IService {
         public String invoke(){
            return new ContainerValue("bar").toString();
    }

作为旁注,如果我ContainerValue将返回类型设置为 on invoke,它就可以工作。

除了使用 ASM 来获取类的依赖项列表之外,还有其他方法吗?为什么这么难?

4

2 回答 2

5

“除了使用 ASM 来获取类的依赖项列表之外,还有其他选择吗?” 那么有几种选择。一种是在没有额外库的情况下实现操作。

“这他妈怎么这么难?” 这并不难。但是,当您需要它来完成一项相当小的任务时,您不应该通过查看一个用于许多不同用例的强大库来判断。

这是一段以简单方式执行整个依赖关系扫描的代码。它非常有效,但一旦你想让它做其他事情,它就会变成一场噩梦。因此,一旦您需要其他字节码操作,我建议您转而使用库。

public static Set<Class<?>> getDependencies(Class<?> from)
  throws IOException, ClassNotFoundException {

  while(from.isArray()) from=from.getComponentType();
  if(from.isPrimitive()) return Collections.emptySet();
  byte[] buf=null;
  int read=0;
  try (InputStream is = from.getResourceAsStream( '/' + from.getName().replace('.', '/') + ".class")) {
    for(int r; ;read+=r) {
      int num=Math.max(is.available()+100, 100);
      if(buf==null) buf=new byte[num];
      else if(buf.length-read<num)
        System.arraycopy(buf, 0, buf=new byte[read+num], 0, read);
      r=is.read(buf, read, buf.length-read);
      if(r<=0) break;
    }
  }
  Set<String> names=getDependencies(ByteBuffer.wrap(buf, 0, read));
  Set<Class<?>> classes=new HashSet<>(names.size());
  ClassLoader cl=from.getClassLoader();
  for(String name:names) classes.add(Class.forName(name, false, cl));
  classes.remove(from);// remove self-reference
  return classes;
}

public static Set<String> getDependencies(ByteBuffer bb) {

  if(bb.getInt()!=0xcafebabe)
    throw new IllegalArgumentException("Not a class file");
  bb.position(8);
  final int numC=bb.getChar();
  BitSet clazz=new BitSet(numC), sign=new BitSet(numC);
  for(int c=1; c<numC; c++) {
    switch(bb.get()) {
      case CONSTANT_Utf8: bb.position(bb.getChar()+bb.position()); break;
      case CONSTANT_Integer: case CONSTANT_Float:
      case CONSTANT_FieldRef: case CONSTANT_MethodRef:
      case CONSTANT_InterfaceMethodRef: case CONSTANT_InvokeDynamic:
        bb.position(bb.position()+4); break;
      case CONSTANT_Long: case CONSTANT_Double:
        bb.position(bb.position()+8); c++; break;
      case CONSTANT_String: bb.position(bb.position()+2); break;
      case CONSTANT_NameAndType:
        bb.position(bb.position()+2);// skip name, fall through:
      case CONSTANT_MethodType: sign.set(bb.getChar()); break;
      case CONSTANT_Class: clazz.set(bb.getChar()); break;
      case CONSTANT_MethodHandle: bb.position(bb.position()+3); break;
      default: throw new IllegalArgumentException(
        "constant pool item type "+(bb.get(bb.position()-1)&0xff));
    }
  }
  bb.position(bb.position()+6);
  bb.position(bb.getChar()*2+bb.position());
  for(int type=0; type<2; type++) { // fields and methods
    int numMember=bb.getChar();
    for(int member=0; member<numMember; member++) {
      bb.position(bb.position()+4);
      sign.set(bb.getChar());
      int numAttr=bb.getChar();
      for(int attr=0; attr<numAttr; attr++) {
        bb.position(bb.position()+2);
        bb.position(bb.getInt()+bb.position());
      }
    }
  }
  bb.position(10);
  HashSet<String> names=new HashSet<>();
  for(int c=1; c<numC; c++) {
    switch(bb.get()) {
      case CONSTANT_Utf8:
        int strSize=bb.getChar(), strStart=bb.position();
        boolean s = sign.get(c);
        if(clazz.get(c))
          if(bb.get(bb.position())=='[') s=true;
          else addName(names, bb, strStart, strSize);
        if(s) addNames(names, bb, strStart, strSize);
        bb.position(strStart+strSize);
        break;
      case CONSTANT_Integer: case CONSTANT_Float:
      case CONSTANT_FieldRef: case CONSTANT_MethodRef:
      case CONSTANT_InterfaceMethodRef: case CONSTANT_NameAndType:
      case CONSTANT_InvokeDynamic:
        bb.position(bb.position()+4); break;
      case CONSTANT_Long: case CONSTANT_Double:
        bb.position(bb.position()+8); c++; break;
      case CONSTANT_String: case CONSTANT_Class:case CONSTANT_MethodType:
        bb.position(bb.position()+2); break;
      case CONSTANT_MethodHandle: bb.position(bb.position()+3); break;
      default: throw new AssertionError();
    }
  }
  return names;
}

private static void addName(HashSet<String> names,
  ByteBuffer src, int s, int strSize) {
  final int e=s+strSize;
  StringBuilder dst=new StringBuilder(strSize);
  ascii: {
    for(;s<e; s++) {
      byte b=src.get(s);
      if(b<0) break ascii;
      dst.append((char)(b=='/'? '.': b));
    }
    names.add(dst.toString());
    return;
  }
  final int oldLimit=src.limit(), oldPos=dst.length();
  src.limit(e).position(s);
  dst.append(StandardCharsets.UTF_8.decode(src));
  src.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 addNames(HashSet<String> names,
  ByteBuffer bb, int s, int l) {
  final int e=s+l;
  for(;s<e; s++) {
    if(bb.get(s)=='L') {
      int p=s+1; while(bb.get(p)!=';') p++;
      addName(names, bb, s+1, p-s-1);
      s=p;
    }
  }
}
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;
于 2013-11-12T11:54:27.397 回答
3

Holger 的回答似乎效果很好,但我也发现代码有点神秘。为了清晰易读,我做了一些研究并重新编写了 Holger 的代码。我添加了一个 main() 作为示例,它过滤掉 java.lang 依赖项(对我来说似乎很混乱)并打印完整的类名。

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class DependencyFinder {

public static void main(String[] args) {
    try {
        // Get dependencies for my class:
        Set<Class<?>> 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<Class<?>> 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<Class<?>> mapClassNamesToClasses(Class<?> from,
        Set<String> names) throws ClassNotFoundException {
    ClassLoader cl = from.getClassLoader();
    Set<Class<?>> 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<String> 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<String> extractClassNamesFromConstantsBasedOnFlags(
        ByteBuffer readBuffer, final int numberOfConstants, ConstantPoolItemFlags flags) throws AssertionError {
    HashSet<String> 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<String> 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<String> 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<String> 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);
    }
}
}
于 2015-07-05T06:51:30.197 回答