Разобрать файл Jar и найти отношения между классами?

Как определить, является ли класс из 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();

person YDev    schedule 05.11.2014    source источник


Ответы (2)


Кто-то проголосовал за то, чтобы закрыть этот вопрос как "слишком широкий". Я не уверен, является ли это подходящей близкой причиной здесь, но может быть, потому что можно рассмотреть этот вопрос (который является продолжением ваш предыдущий вопрос), просто попросив других сделать некоторую работу за вас.

Однако, чтобы ответить на основной вопрос как обнаружить ссылки между классами в одном файле JAR с помощью BCEL:

Вы можете получить список JavaClass объектов из файла JarFile. Для каждого из этих JavaClass объектов вы можете просмотреть Method объекты и их InstructionList. Из этих инструкций вы можете выбрать InvokeInstruction объекты и изучить их дальше, чтобы узнать, какой метод для какого класса фактически вызывается там.

Следующая программа открывает JAR-файл (по понятным причинам это bcel-5.2.jar - он вам все равно понадобится...) и обрабатывает его описанным выше способом. Для каждого JavaClass файла JAR он создает карту из всех объектов 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. Но из-за полиморфизма его можно распознать только как вызов этого метода на "некоторых Object".

person Marco13    schedule 05.11.2014

Отказ от ответственности: Строго говоря, это не ответ на ваш вопрос, поскольку он использует не BCEL, но Javassist. Тем не менее, вы можете найти мой опыт и код полезным.


Несколько лет назад я написал плагин e Maven (я назвал его плагин Storyteller Maven) как раз для этой цели - для анализа JAR-файлов на наличие зависимостей, которые не нужны или не требуются.

Пожалуйста, посмотрите этот вопрос:

Как найти ненужные зависимости в мультипроекте maven?

И мой ответ на него.

Хотя плагин работал, я никогда не выпускал его тогда. Теперь я переместил его на GitHub, чтобы сделать его доступным для других.

Вы спрашиваете о парсинге JAR для анализа кода в .class файлах. Ниже приведены несколько фрагментов кода Javassist.

Отн. ="nofollow noreferrer">Поиск классов в 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 для очень похожей задачи был очень положительным. Надеюсь, это поможет.

person lexicore    schedule 05.11.2014