在运行时获取Java类依赖项列表的最有效方法是什么?
使用this(基于ASM ByteCode Manipulator 3.3.1),我可以执行以下操作:
final Collection<Class<?>> classes =
getClassesUsedBy(MyService.class.getName(), "com");
这会返回对BasicService
和IService
的引用,但会遗漏ContainerValue
,这就是问题所在。我玩过ASM代码,但无法弄清楚如何获取ContainerValue。
package com.foo.bar;
public class MyService extends BasicService implements IService {
public String invoke(){
return new ContainerValue("bar").toString();
}
作为旁注,如果我在ContainerValue
上设置invoke
返回类型,则可行。
有没有其他方法可以使用ASM来获取类的依赖项列表?为什么这么难?
答案 0 :(得分:1)
“有没有其他方法可以使用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.getSimpleName()+".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;
答案 1 :(得分:1)
Holger的答案似乎运作良好,但我也发现代码有点神秘。我做了一些研究并重新编写了Holger的代码,以提高清晰度和可读性。我添加了一个main()作为示例,它过滤掉java.lang依赖项(这对我来说似乎很混乱)并打印完整的类名。
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);
}
}
}