如何以最简洁的方式从包中检索所有类?

How do I retrieve all classes from a package in the neatest way possible?

提问人:Kodigas 提问时间:11/11/2023 最后编辑:Kodigas 更新时间:11/22/2023 访问量:102

问:

首先,我想说的是,我读了这篇 StackOverflow 帖子

我目前的理解是,如果你想从一个包(和子包)中获取所有类,而不写大量的代码行,你基本上会被 API 所困。以下是一些选项:Reflections

选项 1.

    private static Set<Class<?>> getClassesFromPackage(String packageName) {
        Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
        return new HashSet<>(reflections.getSubTypesOf(Object.class));
    }

非最佳:已弃用,因此您会收到警告SubTypesScanner

选项 2.

    private static Set<Class<?>> getClassesFromPackage(String packageName) {
        Reflections reflections = new Reflections(packageName, Scanners.SubTypes.filterResultsBy(s -> true));
        return new HashSet<>(reflections.getSubTypesOf(Object.class));
    }

不是最佳的:是笨拙的自动对焦,所以你要么得到不容易阅读的代码,要么你必须提供一些元评论Scanners.SubTypes.filterResultsBy(s -> true)

    private static Set<Class<?>> getClassesFromPackage(String packageName) {
        Reflections reflections = new Reflections(packageName, getStuffThatMakesItWork());
        return new HashSet<>(reflections.getSubTypesOf(Object.class));
    }

    private static Scanners getStuffThatMakesItWork() {
        return Scanners.SubTypes.filterResultsBy(s -> true);
    }

如果我能写这个,那岂不是很桃色

    private static Set<Class<?>> getClassesFromPackage(String packageName) {
        Reflections reflections = new Reflections(packageName);
        return new HashSet<>(reflections.getSubTypesOf(Object.class));
    }

它的可读性要高得多,但遗憾的是它不起作用(返回的集合将包含零项)

扫描包类的最佳方法是什么?

标准:

  1. 代码应尽可能短。
  2. 代码应该是可读的(随机的人在查看它时不应该得到任何 WTF 时刻)
  3. 您不应收到警告

你的解决方案本质上应该实现这个接口(你可能没有在你的答案中明确地这样做):

public interface PackageScanner {
    Set<Class<?>> getClassesFromPackage(String packageName);
}

我还没有提到的一件事是,自 2021 年以来,整个库都没有维护过,因此依赖它可能不是一个可靠的长期解决方案。有没有至少同样好的替代品?Reflections

更新:我试图实现 Reilas 的建议,但它导致了一个非常尴尬的代码Class.forName()

// here I reveal that all along I actually wanted to scan for all entities and DTOs
    private static void scanForEntitiesAndDtos() {
        File sourceRoot = new File("");
        entitiesAndDtos.addAll(
                    Arrays.stream(Objects.requireNonNull(sourceRoot.listFiles()))
                            .map(clazz -> {
                                try {
                                    return Class.forName(clazz.getPath());
                                } catch (ClassNotFoundException e) {
                                    throw new RuntimeException(e);
                                }
                            })
                            .filter(BasicDataGeneratorTest::isEntityOrDto)
                            .collect(Collectors.toSet())
        );
    }

    private static boolean isEntityOrDto(Class<?> clazz) {
        return isEntity(clazz) || isDto(clazz);
    }

    private static boolean isEntity(Class<?> clazz) {
        return clazz.getAnnotation(Entity.class) != null;
    }

    private static boolean isDto(Class<?> clazz) {
        return clazz.getSimpleName().endsWith("Dto");
    }

此外,它不起作用,当涉及到 .我尝试过的事情:Objects.requireNotNull()

  • new File("")
  • new File("com.example.projectname")
  • new File("com/example/projectname")
  • new File("src.main.java.com.example.projectname")
  • new File("src/main/java/com/example/projectname")

我想这是因为不扫描子目录(谁知道呢,该方法的文档对此保持沉默),但是如果您从 返回的字符串中创建 s,则过滤它们,然后将它们转换为类使用 ,它会变得更加笨拙(假设它都可以工作)listFiles()Filefile.list()File::isFileClass.forName(file.getPath())

Java 反射

评论

1赞 Turing85 11/11/2023
"“如何”只不过是为什么?”功能之后的形式。这篇文章读起来像一个 XY 问题。那么,为什么要在一个包中找到所有类呢?
1赞 Reilas 11/11/2023
你能不能只看目录?
2赞 Holger 11/13/2023
“包中的所有类”是否包括可能在运行时创建的类?
2赞 Holger 11/15/2023
您发布了非工作示例代码。读者可以从中猜到,您希望过滤类以具有以“Dto”结尾的名称或注释。那不是目的。这仍然只是一个尝试的解决方案Entity
2赞 Holger 11/15/2023
嗯,这已经是有价值的信息了。所以你说的是你自己的包,其中包含你控制的类。因此,您还可以指定软件包是否应该具有单个位置¹,或者可以分布在多个类路径条目中,以及部署形式是固定的(例如,始终是 jar 文件)还是需要支持不同的形式。(¹ 或者您是否正在使用模块系统,无论如何它都需要单一来源)您希望支持哪个最低 Java 版本,是否可以选择让类实现标记接口?等。

答:

0赞 kaqqao 11/22/2023 #1

您声称已经阅读的问题已经有一个答案,其中提到了 ClassGraph,这是一个类似于 Reflections 的库,但很好。

try (ScanResult scanResults = new ClassGraph().acceptPackages(packageName)
        .enableClassInfo().scan()) {

    Set<Class<?>> entitiesAndDtos = scanResults
        .getAllClasses().stream()
        .filter(BasicDataGeneratorTest::isEntityOrDto)
        .map(ClassInfo::loadClass)
        .collect(Collectors.toSet();
}

Jandex 也是一个不错的选择。

评论

0赞 Kodigas 11/23/2023
谢谢,但你不得不承认它更笨拙