在运行时查找java类依赖项

raf*_*ian 4 java bytecode class

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

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

final Collection<Class<?>> classes = 
  getClassesUsedBy(MyService.class.getName(), "com");
Run Code Online (Sandbox Code Playgroud)

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

package com.foo.bar;

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

附带说明一下,如果我将ContainerValue返回类型设置为 on invoke,它就可以工作。

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

Hol*_*ger 6

\xe2\x80\x9c除了使用 ASM 获取类的依赖项列表之外,还有其他选择吗?\xe2\x80\x9d\n有几种选择。一是无需额外的库即可实现操作。

\n

\xe2\x80\x9c为什么这么难?\xe2\x80\x9d\n\xe2\x80\x99s 没那么难。但是,当您需要一个强大的库来完成一项相当小的任务时,您不应该通过查看一个用于许多不同用例的强大库来进行判断。

\n

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

\n
public static Set<Class<?>> getDependencies(Class<?> from)\n  throws IOException, ClassNotFoundException {\n\n  while(from.isArray()) from=from.getComponentType();\n  if(from.isPrimitive()) return Collections.emptySet();\n  byte[] buf=null;\n  int read=0;\n  try (InputStream is = from.getResourceAsStream( '/' + from.getName().replace('.', '/') + ".class")) {\n    for(int r; ;read+=r) {\n      int num=Math.max(is.available()+100, 100);\n      if(buf==null) buf=new byte[num];\n      else if(buf.length-read<num)\n        System.arraycopy(buf, 0, buf=new byte[read+num], 0, read);\n      r=is.read(buf, read, buf.length-read);\n      if(r<=0) break;\n    }\n  }\n  Set<String> names=getDependencies(ByteBuffer.wrap(buf, 0, read));\n  Set<Class<?>> classes=new HashSet<>(names.size());\n  ClassLoader cl=from.getClassLoader();\n  for(String name:names) classes.add(Class.forName(name, false, cl));\n  classes.remove(from);// remove self-reference\n  return classes;\n}\n\npublic static Set<String> getDependencies(ByteBuffer bb) {\n\n  if(bb.getInt()!=0xcafebabe)\n    throw new IllegalArgumentException("Not a class file");\n  bb.position(8);\n  final int numC=bb.getChar();\n  BitSet clazz=new BitSet(numC), sign=new BitSet(numC);\n  for(int c=1; c<numC; c++) {\n    switch(bb.get()) {\n      case CONSTANT_Utf8: bb.position(bb.getChar()+bb.position()); break;\n      case CONSTANT_Integer: case CONSTANT_Float:\n      case CONSTANT_FieldRef: case CONSTANT_MethodRef:\n      case CONSTANT_InterfaceMethodRef: case CONSTANT_InvokeDynamic:\n        bb.position(bb.position()+4); break;\n      case CONSTANT_Long: case CONSTANT_Double:\n        bb.position(bb.position()+8); c++; break;\n      case CONSTANT_String: bb.position(bb.position()+2); break;\n      case CONSTANT_NameAndType:\n        bb.position(bb.position()+2);// skip name, fall through:\n      case CONSTANT_MethodType: sign.set(bb.getChar()); break;\n      case CONSTANT_Class: clazz.set(bb.getChar()); break;\n      case CONSTANT_MethodHandle: bb.position(bb.position()+3); break;\n      default: throw new IllegalArgumentException(\n        "constant pool item type "+(bb.get(bb.position()-1)&0xff));\n    }\n  }\n  bb.position(bb.position()+6);\n  bb.position(bb.getChar()*2+bb.position());\n  for(int type=0; type<2; type++) { // fields and methods\n    int numMember=bb.getChar();\n    for(int member=0; member<numMember; member++) {\n      bb.position(bb.position()+4);\n      sign.set(bb.getChar());\n      int numAttr=bb.getChar();\n      for(int attr=0; attr<numAttr; attr++) {\n        bb.position(bb.position()+2);\n        bb.position(bb.getInt()+bb.position());\n      }\n    }\n  }\n  bb.position(10);\n  HashSet<String> names=new HashSet<>();\n  for(int c=1; c<numC; c++) {\n    switch(bb.get()) {\n      case CONSTANT_Utf8:\n        int strSize=bb.getChar(), strStart=bb.position();\n        boolean s = sign.get(c);\n        if(clazz.get(c))\n          if(bb.get(bb.position())=='[') s=true;\n          else addName(names, bb, strStart, strSize);\n        if(s) addNames(names, bb, strStart, strSize);\n        bb.position(strStart+strSize);\n        break;\n      case CONSTANT_Integer: case CONSTANT_Float:\n      case CONSTANT_FieldRef: case CONSTANT_MethodRef:\n      case CONSTANT_InterfaceMethodRef: case CONSTANT_NameAndType:\n      case CONSTANT_InvokeDynamic:\n        bb.position(bb.position()+4); break;\n      case CONSTANT_Long: case CONSTANT_Double:\n        bb.position(bb.position()+8); c++; break;\n      case CONSTANT_String: case CONSTANT_Class:case CONSTANT_MethodType:\n        bb.position(bb.position()+2); break;\n      case CONSTANT_MethodHandle: bb.position(bb.position()+3); break;\n      default: throw new AssertionError();\n    }\n  }\n  return names;\n}\n\nprivate static void addName(HashSet<String> names,\n  ByteBuffer src, int s, int strSize) {\n  final int e=s+strSize;\n  StringBuilder dst=new StringBuilder(strSize);\n  ascii: {\n    for(;s<e; s++) {\n      byte b=src.get(s);\n      if(b<0) break ascii;\n      dst.append((char)(b=='/'? '.': b));\n    }\n    names.add(dst.toString());\n    return;\n  }\n  final int oldLimit=src.limit(), oldPos=dst.length();\n  src.limit(e).position(s);\n  dst.append(StandardCharsets.UTF_8.decode(src));\n  src.limit(oldLimit);\n  for(int pos=oldPos, len=dst.length(); pos<len; pos++)\n    if(dst.charAt(pos)=='/') dst.setCharAt(pos, '.');\n  names.add(dst.toString());\n  return;\n}\n\nprivate static void addNames(HashSet<String> names,\n  ByteBuffer bb, int s, int l) {\n  final int e=s+l;\n  for(;s<e; s++) {\n    if(bb.get(s)=='L') {\n      int p=s+1; while(bb.get(p)!=';') p++;\n      addName(names, bb, s+1, p-s-1);\n      s=p;\n    }\n  }\n}\nprivate static final byte CONSTANT_Utf8 = 1, CONSTANT_Integer = 3,\n  CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6,\n  CONSTANT_Class = 7, CONSTANT_String = 8, CONSTANT_FieldRef = 9,\n  CONSTANT_MethodRef = 10, CONSTANT_InterfaceMethodRef = 11,\n  CONSTANT_NameAndType = 12, CONSTANT_MethodHandle = 15,\n  CONSTANT_MethodType = 16, CONSTANT_InvokeDynamic = 18;\n
Run Code Online (Sandbox Code Playgroud)\n

  • @raffian 有机会看到您的 ASM 解决方案的帖子吗?:-) (3认同)
  • @mmm,当您将 `from.getSimpleName()+".class"` 替换为 `'/'+from.getName().replace('.', '/')+".class"` 时,它将适用于内部类和匿名/本地类也是如此。如果您觉得有用,可以给答案点赞…… (2认同)