查找使用给定API的哪些类

11
在我的Java项目中,我想以编程的方式找出使用了给定API的哪些类。有什么好方法可以做到这一点吗?也许是通过源代码解析或字节码解析吗?因为我担心反射不会有任何作用。
为了简化事情:在我的项目中没有通配符导入(import com.mycompany.api.*;),也没有完全限定的字段或变量定义(private com.mycompany.api.MyThingy thingy;)或任何Class.forName(...)结构。考虑到这些限制,我想这就归结为解析导入语句了。有没有首选的方法来做到这一点?

Bash脚本(例如使用egrep)不是一个选项吗? - aioobe
不是非常的。a) 我需要它在Java代码中,b) 它需要在Windows和* x上运行。 - Sean Patrick Floyd
8个回答

13

你可以使用ASMRemapper类来发现类(信不信由你)。实际上,这个类的作用是在字节码中替换所有出现的类名。但是对于你的目的,它不需要替换任何东西。

这可能听起来有点难以理解,所以这里有一个例子...

首先,你需要创建一个Remapper的子类,其唯一的目的就是拦截对mapType(String)方法的所有调用,并记录其参数以供稍后使用。

public class ClassNameRecordingRemapper extends Remapper {

    private final Set<? super String> classNames;

    public ClassNameRecordingRemapper(Set<? super String> classNames) {
        this.classNames = classNames;
    }

    @Override
    public String mapType(String type) {
        classNames.add(type);
        return type;
    }

}

现在你可以编写这样的方法:

public Set<String> findClassNames(byte[] bytecode) {
    Set<String> classNames = new HashSet<String>();

    ClassReader classReader = new ClassReader(bytecode);
    ClassWriter classWriter = new ClassWriter(classReader, 0);

    ClassNameRecordingRemapper remapper = new ClassNameRecordingRemapper(classNames);
    classReader.accept(remapper, 0);

    return classNames;
}

实际上,获取所有类的字节码是您的责任。


由seanizer(OP)编辑

我接受了这个答案,但由于上面的代码不完全正确,我将插入我使用的方法:

public static class Collector extends Remapper{

    private final Set<Class<?>> classNames;
    private final String prefix;

    public Collector(final Set<Class<?>> classNames, final String prefix){
        this.classNames = classNames;
        this.prefix = prefix;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String mapDesc(final String desc){
        if(desc.startsWith("L")){
            this.addType(desc.substring(1, desc.length() - 1));
        }
        return super.mapDesc(desc);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String[] mapTypes(final String[] types){
        for(final String type : types){
            this.addType(type);
        }
        return super.mapTypes(types);
    }

    private void addType(final String type){
        final String className = type.replace('/', '.');
        if(className.startsWith(this.prefix)){
            try{
                this.classNames.add(Class.forName(className));
            } catch(final ClassNotFoundException e){
                throw new IllegalStateException(e);
            }
        }
    }

    @Override
    public String mapType(final String type){
        this.addType(type);
        return type;
    }

}

public static Set<Class<?>> getClassesUsedBy(
    final String name,   // class name
    final String prefix  // common prefix for all classes
                         // that will be retrieved
    ) throws IOException{
    final ClassReader reader = new ClassReader(name);
    final Set<Class<?>> classes =
        new TreeSet<Class<?>>(new Comparator<Class<?>>(){

            @Override
            public int compare(final Class<?> o1, final Class<?> o2){
                return o1.getName().compareTo(o2.getName());
            }
        });
    final Remapper remapper = new Collector(classes, prefix);
    final ClassVisitor inner = new EmptyVisitor();
    final RemappingClassAdapter visitor =
        new RemappingClassAdapter(inner, remapper);
    reader.accept(visitor, 0);
    return classes;
}

这是用于测试的主类:

public static void main(final String[] args) throws Exception{
    final Collection<Class<?>> classes =
        getClassesUsedBy(Collections.class.getName(), "java.util");
    System.out.println("Used classes:");
    for(final Class<?> cls : classes){
        System.out.println(" - " + cls.getName());
    }

}

这里是输出结果:

Used classes:
 - java.util.ArrayList
 - java.util.Arrays
 - java.util.Collection
 - java.util.Collections
 - java.util.Collections$1
 - java.util.Collections$AsLIFOQueue
 - java.util.Collections$CheckedCollection
 - java.util.Collections$CheckedList
 - java.util.Collections$CheckedMap
 - java.util.Collections$CheckedRandomAccessList
 - java.util.Collections$CheckedSet
 - java.util.Collections$CheckedSortedMap
 - java.util.Collections$CheckedSortedSet
 - java.util.Collections$CopiesList
 - java.util.Collections$EmptyList
 - java.util.Collections$EmptyMap
 - java.util.Collections$EmptySet
 - java.util.Collections$ReverseComparator
 - java.util.Collections$ReverseComparator2
 - java.util.Collections$SelfComparable
 - java.util.Collections$SetFromMap
 - java.util.Collections$SingletonList
 - java.util.Collections$SingletonMap
 - java.util.Collections$SingletonSet
 - java.util.Collections$SynchronizedCollection
 - java.util.Collections$SynchronizedList
 - java.util.Collections$SynchronizedMap
 - java.util.Collections$SynchronizedRandomAccessList
 - java.util.Collections$SynchronizedSet
 - java.util.Collections$SynchronizedSortedMap
 - java.util.Collections$SynchronizedSortedSet
 - java.util.Collections$UnmodifiableCollection
 - java.util.Collections$UnmodifiableList
 - java.util.Collections$UnmodifiableMap
 - java.util.Collections$UnmodifiableRandomAccessList
 - java.util.Collections$UnmodifiableSet
 - java.util.Collections$UnmodifiableSortedMap
 - java.util.Collections$UnmodifiableSortedSet
 - java.util.Comparator
 - java.util.Deque
 - java.util.Enumeration
 - java.util.Iterator
 - java.util.List
 - java.util.ListIterator
 - java.util.Map
 - java.util.Queue
 - java.util.Random
 - java.util.RandomAccess
 - java.util.Set
 - java.util.SortedMap
 - java.util.SortedSet

听起来正是我正在寻找的,我会看一下的,谢谢 (+1) - Sean Patrick Floyd
看起来很棒!我特别喜欢输出。我刚刚意识到你收到的“类名”似乎是内部类名。ASM提供了Type类来帮助您解决这个问题。例如,您可以使用Type type = Type.getType(internalClassName)。从这里开始,您可以调用type.getClassName(),这就是您所熟悉的类名。 - Adam Paynter
@Bohzo:这是一个非常有趣的解决方案,用来弥补 Stack Overflow 缺乏消息系统的不足! :) 我希望你能得到那个答案。 - Adam Paynter
我喜欢这个,但它错过了方法内部使用的类;是否有办法也获取这些引用? - raffian
非常酷。看起来EmptyVisitor已被移除。要使用什么替代品 https://github.com/bmc/javautil/pull/15/files?diff=split? - user48956

4

a) 感谢您的回答。 b) 我认为这两个都不会有帮助,因为我想要基于结果自动运行一些Java代码。 - Sean Patrick Floyd
依赖关系查找器是开源的。您可以在您的项目中检查和集成该代码,或者对生成的报告进行文本分析。 - Faisal Feroz
1
谢谢回答(+1),但这里列出了几种完全符合我要求的技术,所以我不会检查关闭的调用。 - Sean Patrick Floyd

2

我目前正在使用bcel进行字节码分析,但这真是一件痛苦的事情。我更喜欢使用编译器树API,但从哪里开始呢?(我没有编译任务和处理环境) - Sean Patrick Floyd
您可以在源代码上运行注解处理器 - http://download.oracle.com/javase/6/docs/api/javax/annotation/processing/AbstractProcessor.html,以获取ProcessingEnvironment。 - emory
我知道如何启动注解处理器,但我不想在编译过程中执行此操作,也不想启动第二个编译过程。 - Sean Patrick Floyd
我不知道如何在不使用编译过程的情况下使用编译器树 API。这可能不适合你。 - emory

1

也许是这样的:

import java.io.*;
import java.util.Scanner;
import java.util.regex.Pattern;

public class FileTraverser {

    public static void main(String[] args) {
        visitAllDirsAndFiles(new File("source_directory"));
    }

    public static void visitAllDirsAndFiles(File root) {
        if (root.isDirectory())
            for (String child : root.list())
                visitAllDirsAndFiles(new File(root, child));
        process(root);
    }

    private static void process(File f) {

        Pattern p = Pattern.compile("(?=\\p{javaWhitespace}*)import (.*);");
        if (f.isFile() && f.getName().endsWith(".java")) {
            try {
                Scanner s = new Scanner(f);
                String cls = "";
                while (null != (cls = s.findWithinHorizon(p, 0)))
                    System.out.println(cls);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
}

你可能需要考虑注释,但这不应该太难。你也可以确保只在类声明之前查找导入。


这正是我自己会想到的解决方案(+1),但我正在寻找一种能够理解源代码或字节码的解决方案。 - Sean Patrick Floyd

1

我正是出于这个目的使用DependencyFinder。它可以分析字节码并提取所有依赖关系,然后倾倒为txt或xml格式的报告(参见DependencyExtractor工具)。您应该能够以编程方式分析应用程序代码中的报告。

我已经将其集成到我的构建过程中,以检查某些API是否被应用程序使用。


我会点赞这个答案,因为它很好,但我不认为DependencyFinder和我能成为朋友 :-) - Sean Patrick Floyd

0

你可能想使用STAN进行操作。

“耦合视图”以一个漂亮的图形展示了API之间的依赖关系。


很好,但这并不是我问题的答案,我需要一种编程方式来获取使用的类,而不是一个漂亮的报告。 - Sean Patrick Floyd

0

下次请仔细阅读问题和其他答案。我在寻找可编程使用的内容,你所建议的是一个不错的工具,但并不是我问题的答案。 - Sean Patrick Floyd

0

感谢Adam Paynter,这很有帮助。但是我要找的是递归获取依赖类-也就是从一个项目中提取特性。因此,需要获取与特定类相关联的所有类,以及这些类的使用类等等。还要获取JAR文件。因此,我创建了自己的Java依赖关系解析器项目,该项目将为项目中的特定类查找依赖类/ JAR。我在这里分享它,可以为某些人提供帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接