【问题标题】:Find out which classes of a given API are used找出使用给定 API 的哪些类
【发布时间】:2011-04-13 16:26:55
【问题描述】:

在我的一个 Java 项目中,我想以编程方式找出使用了给定 API 中的哪些类。有什么好的方法吗?通过源代码解析或字节码解析可能吗?因为反射没有任何用处,恐怕。

为了简单起见:我的项目中没有通配符导入 (import com.mycompany.api.*;),没有完全限定的字段或变量定义 (private com.mycompany.api.MyThingy thingy;),也没有任何 Class.forName(...) 构造。鉴于这些限制,我猜它归结为解析导入语句。有没有首选的方法来做到这一点?

【问题讨论】:

  • Bash 脚本(例如使用 egrep)不是一个选项?
  • 并非如此。 a) 我需要在 java 代码中使用它,b) 它需要在 windows 和 *x 上运行

标签: java reflection import bytecode


【解决方案1】:

我认为以下内容可能会对您有所帮助:

  1. Class Dependency Analyzer
  2. Dependency Finder

【讨论】:

  • a) 感谢您的回答 b) 我认为这些都没有帮助,因为我想根据结果自动运行一些 java 代码。
  • 依赖查找器是开源的。您可以签出并将代码集成到您的项目中,也可以对生成的报告进行文本分析。
  • 感谢您的回答 (+1),但这里列出的几种技术完全符合我的要求,因此我不会检查近距离电话。
【解决方案2】:

可能是这样的:

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();
            }
        }
    }
}

您可能希望将 cmets 考虑在内,但这应该不会太难。您还可以确保只在类声明之前查找导入。

【讨论】:

  • 这正是我自己破解解决方案的方式(+1 :-)),但我正在寻找一个真正理解源代码或字节码的解决方案。
【解决方案3】:

您可以使用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)
  • 这看起来很时髦!我特别喜欢输出。我刚刚意识到您收到的“类名”似乎是 internal 类名。 ASM 提供了Type 类来帮助您解决这个问题。例如,您可以使用Type type = Type.getType(internalClassName)。然后你可以从这里调用type.getClassName(),这是你习惯的类名。
  • @Bohzo:这是 Stack Overflow 缺乏消息传递系统的一个有趣的解决方法! :) 我希望你有这个答案。
  • 我喜欢这个,但它错过了方法内部使用的类;有什么方法可以获取这些参考资料吗?
  • 非常酷。看起来 EmptyVisitor 已被删除。用什么代替 github.com/bmc/javautil/pull/15/files?diff=split
【解决方案4】:
  1. Compiler Tree API
  2. 字节码分析——全限定名应该在常量池中

【讨论】:

  • 我目前正在使用 bcel 进行字节码分析,但这很痛苦。我更喜欢 Compiler Tree API,但起点是什么? (我没有编译任务也没有处理环境)
  • 您可以在您的源代码上运行注释处理器 - download.oracle.com/javase/6/docs/api/javax/annotation/… 以获得 ProcessingEnvironment
  • 我知道如何启动注释处理器,但我不想在编译过程中执行此操作,也不想启动第二个编译过程。
  • 我不知道如何在不使用编译过程的情况下使用编译器树 API。这可能不适合你。
【解决方案5】:

我使用DependencyFinder 正是为了这个目的。它可以分析字节码并提取所有依赖项,然后以 txt 或 xml 格式转储报告(请参阅 DependencyExtractor 工具)。您应该能够通过应用程序代码以编程方式分析报告。

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

【讨论】:

  • 我会投赞成票,因为这是一个很好的答案,但我不认为 DependencyFinder 和我可以成为朋友 :-)
【解决方案6】:

您可能想为此使用STAN

“耦合视图”以漂亮的图表将 API 的依赖关系可视化。

【讨论】:

  • 很好,但不是我的问题的答案,我需要一种编程方式来获取使用过的课程,而不是漂亮的报告。
【解决方案7】:

如果您使用 Eclipse。尝试使用分析工具。它不仅告诉我们正在使用哪些类,而且还提供了更多关于它的信息。结果将类似于:

有一个非常好的快速入门:

http://www.eclipse.org/tptp/home/documents/tutorials/profilingtool/profilingexample_32.html

【讨论】:

  • 下次尝试仔细阅读问题和其他答案。我正在寻找可以以编程方式使用的东西。您的建议是一个不错的工具,但不是我问题的答案。
【解决方案8】:

感谢 Adam Paynter,它帮助了我。但我一直在寻找的是(递归地)获取依赖类——这意味着从项目中获取一个特性。因此,需要获取与特定类关联的所有类,并再次获取这些类的使用类,依此类推。并得到罐子。所以,我创建了自己的Java Dependency Resolver project,它将在项目中找到特定类的依赖类/jar。我在这里分享它,可能会用到一些身体。

【讨论】:

    猜你喜欢
    • 2016-09-16
    • 1970-01-01
    • 1970-01-01
    • 2011-08-20
    • 1970-01-01
    • 2018-01-05
    • 2012-08-06
    • 1970-01-01
    • 2010-09-16
    相关资源
    最近更新 更多