繁体   English   中英

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

[英]Parse Jar file and find relationships between classes?

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

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

        String jarfile = "C:\\Users\\OOOO\\Desktop\\Sample.Jar";

        jar = new JarFile(jarfile);
        Enumeration<JarEntry> 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<String, JavaClass> javaClasses = 
            collectJavaClasses(jarName, jarFile);

        for (JavaClass javaClass : javaClasses.values())
        {
            System.out.println("Class "+javaClass.getClassName());
            Map<JavaClass, Set<Method>> references = 
                computeReferences(javaClass, javaClasses);
            for (Entry<JavaClass, Set<Method>> entry : references.entrySet())
            {
                JavaClass referencedJavaClass = entry.getKey();
                Set<Method> 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<String, JavaClass> collectJavaClasses(
        String jarName, JarFile jarFile) 
            throws ClassFormatException, IOException
    {
        Map<String, JavaClass> javaClasses =
            new LinkedHashMap<String, JavaClass>();
        Enumeration<JarEntry> 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<JavaClass, Set<Method>> computeReferences(
        JavaClass javaClass, Map<String, JavaClass> knownJavaClasses) 
            throws ClassNotFoundException
    {
        Map<JavaClass, Set<Method>> references = 
            new LinkedHashMap<JavaClass, Set<Method>>();
        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<Method> methods = references.get(referencedJavaClass);
                if (methods == null)
                {
                    methods = new LinkedHashSet<Method>();
                    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<JarEntry> 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<String> referencedClassNames = ctClass.getRefClasses();

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM