解析Jar文件并找到类之间的关系?

如何检测jar文件中的类是否正在扩展其他类,或者是否创建了对其他类对象或其他类对象的方法调用? 然后系统输出哪个类扩展哪个类以及哪个类调用哪个类的方法。

我使用Classparser来解析jar。 这是我的代码的一部分:

String jarfile = "C:\\Users\\OOOO\\Desktop\\Sample.Jar"; jar = new JarFile(jarfile); Enumeration entries = jar.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (!entry.getName().endsWith(".class")) { continue; } ClassParser parser = new ClassParser(jarfile, entry.getName()); JavaClass javaClass = parser.parse(); 

有人投票将此问题视为“过于宽泛”。 我不确定这是否是适当的接近原因,但可能是因为人们可以考虑这个问题(这是对你之前的问题的跟进),只是要求别人为你做一些工作。

但是,要回答如何使用BCEL检测单个JAR文件中的类之间的引用的基本问题:

您可以从JarFile获取JavaClass对象的列表。 对于每个JavaClass对象,您可以检查Method对象及其InstructionList 。 在这些指令中,您可以选择InvokeInstruction对象并进一步检查它们以找出在那里实际调用哪个类的方法。

以下程序打开一个JAR文件(出于显而易见的原因,它是bcel-5.2.jar – 你无论如何都需要它……)并以上述方式处理它。 对于JAR文件的每个JavaClass ,它将从所有引用的JavaClass对象创建一个映射到在这些类上调用的Method列表,并相应地打印信息:

 import java.io.IOException; import java.util.Arrays; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.bcel.classfile.ClassFormatException; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.ConstantPool; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InvokeInstruction; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.ObjectType; import org.apache.bcel.generic.ReferenceType; import org.apache.bcel.generic.Type; public class BCELRelationships { public static void main(String[] args) throws Exception { JarFile jarFile = null; try { String jarName = "bcel-5.2.jar"; jarFile = new JarFile(jarName); findReferences(jarName, jarFile); } catch (Exception e) { e.printStackTrace(); } finally { if (jarFile != null) { try { jarFile.close(); } catch (IOException e) { e.printStackTrace(); } } } } private static void findReferences(String jarName, JarFile jarFile) throws ClassFormatException, IOException, ClassNotFoundException { Map javaClasses = collectJavaClasses(jarName, jarFile); for (JavaClass javaClass : javaClasses.values()) { System.out.println("Class "+javaClass.getClassName()); Map> references = computeReferences(javaClass, javaClasses); for (Entry> entry : references.entrySet()) { JavaClass referencedJavaClass = entry.getKey(); Set methods = entry.getValue(); System.out.println( " is referencing class "+ referencedJavaClass.getClassName()+" by calling"); for (Method method : methods) { System.out.println( " "+method.getName()+" with arguments "+ Arrays.toString(method.getArgumentTypes())); } } } } private static Map collectJavaClasses( String jarName, JarFile jarFile) throws ClassFormatException, IOException { Map javaClasses = new LinkedHashMap(); Enumeration entries = jarFile.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); if (!entry.getName().endsWith(".class")) { continue; } ClassParser parser = new ClassParser(jarName, entry.getName()); JavaClass javaClass = parser.parse(); javaClasses.put(javaClass.getClassName(), javaClass); } return javaClasses; } public static Map> computeReferences( JavaClass javaClass, Map knownJavaClasses) throws ClassNotFoundException { Map> references = new LinkedHashMap>(); ConstantPool cp = javaClass.getConstantPool(); ConstantPoolGen cpg = new ConstantPoolGen(cp); for (Method m : javaClass.getMethods()) { String fullClassName = javaClass.getClassName(); String className = fullClassName.substring(0, fullClassName.length()-6); MethodGen mg = new MethodGen(m, className, cpg); InstructionList il = mg.getInstructionList(); if (il == null) { continue; } InstructionHandle[] ihs = il.getInstructionHandles(); for(int i=0; i < ihs.length; i++) { InstructionHandle ih = ihs[i]; Instruction instruction = ih.getInstruction(); if (!(instruction instanceof InvokeInstruction)) { continue; } InvokeInstruction ii = (InvokeInstruction)instruction; ReferenceType referenceType = ii.getReferenceType(cpg); if (!(referenceType instanceof ObjectType)) { continue; } ObjectType objectType = (ObjectType)referenceType; String referencedClassName = objectType.getClassName(); JavaClass referencedJavaClass = knownJavaClasses.get(referencedClassName); if (referencedJavaClass == null) { continue; } String methodName = ii.getMethodName(cpg); Type[] argumentTypes = ii.getArgumentTypes(cpg); Method method = findMethod(referencedJavaClass, methodName, argumentTypes); Set methods = references.get(referencedJavaClass); if (methods == null) { methods = new LinkedHashSet(); references.put(referencedJavaClass, methods); } methods.add(method); } } return references; } private static Method findMethod( JavaClass javaClass, String methodName, Type argumentTypes[]) throws ClassNotFoundException { for (Method method : javaClass.getMethods()) { if (method.getName().equals(methodName)) { if (Arrays.equals(argumentTypes, method.getArgumentTypes())) { return method; } } } for (JavaClass superClass : javaClass.getSuperClasses()) { Method method = findMethod(superClass, methodName, argumentTypes); if (method != null) { return method; } } return null; } } 

但请注意,此信息在各方面可能都不完整。 例如,由于多态性,您可能无法始终检测到某个类的对象上调用了某个方法,因为它隐藏在多态抽象背后。 例如,在代码片段中

 void call() { MyClass m = new MyClass(); callToString(m); } void callToString(Object object) { object.toString(); } 

toString的调用实际上发生在MyClass一个实例上。 但是由于多态性,它只能被识别为在“some Object ”上调用此方法。

免责声明:严格来说,这不是您问题的答案,因为它不使用BCEL而是使用Javassist 。 不过,您可能会发现我的经验和代码很有用。


几年前我为此目的编写了e Maven插件(我称之为Storyteller Maven插件 ) – 分析JAR文件中的依赖关系,这是不必要的或不需要的。

请看这个问题:

如何在maven多项目中找到不必要的依赖项?

我的回答 。

虽然插件工作,但我从来没有发布它。 现在我把它移到GitHub只是为了让别人可以访问它。

您询问解析JAR以分析.class文件中的代码。 下面是几个Javassist代码片段。

在JAR文件中搜索类,并为每个条目创建一个CtClass :

 final JarFile artifactJarFile = new JarFile(artifactFile); final Enumeration jarEntries = artifactJarFile .entries(); while (jarEntries.hasMoreElements()) { final JarEntry jarEntry = jarEntries.nextElement(); if (jarEntry.getName().endsWith(".class")) { InputStream is = null; CtClass ctClass = null; try { is = artifactJarFile.getInputStream(jarEntry); ctClass = classPool.makeClass(is); } catch (IOException ioex1) { throw new MojoExecutionException( "Could not load class from JAR entry [" + artifactFile.getAbsolutePath() + "/" + jarEntry.getName() + "]."); } finally { try { if (is != null) is.close(); } catch (IOException ignored) { // Ignore } } // ... } } 

找出引用的类只是 :

 final Collection referencedClassNames = ctClass.getRefClasses(); 

总的来说,我对Javassist的非常相似的任务经验非常积极。 我希望这有帮助。

    Interesting Posts