【问题标题】:How do I read all classes from a Java package in the classpath?如何从类路径中的 Java 包中读取所有类?
【发布时间】:2010-11-30 05:25:51
【问题描述】:

我需要读取 Java 包中包含的类。这些类在类路径中。我需要直接从 Java 程序执行此任务。你知道一个简单的方法吗?

List<Class> classes = readClassesFrom("my.package")

【问题讨论】:

标签: java reflection


【解决方案1】:

如果你的类路径中有Spring,那么下面的代码就可以了。

查找包中所有使用 XmlRootElement 注释的类:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

【讨论】:

【解决方案2】:

我用这个,它适用于文件或 jar 档案

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}

【讨论】:

  • 如果你去掉对 File.Separator 的引用并且只使用'/'就可以了
  • 当路径包含空格时,需要进行更多更改才能使用 jar 文件。您必须解码 jar 文件路径。更改:jarFileName= packageURL.getFile();至:jarFileName= URLDecoder.decode(packageURL.getFile());
  • 我只得到 jar 中的包名..没有得到类名
  • new File(uri) 将解决您的空格问题。
  • entryName.lastIndexOf('.') 将是 -1
【解决方案3】:

您可以使用 here 中描述的反射项目

它非常完整且易于使用。

来自上述网站的简要说明:

Reflections 扫描你的类路径,索引元数据,允许你 在运行时查询它,并且可以为许多人保存和收集该信息 项目中的模块。

例子:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

【讨论】:

  • Equinox 可以工作吗?如果是这样,它可以在不激活插件的情况下这样做吗?
  • 适用于春分。在旧版本的 Reflections 上,添加 bundle jar vfsType。见here
【解决方案4】:

Spring 在PathMatchingResourcePatternResolver 中实现了出色的类路径搜索功能。如果您使用classpath*: 前缀,您可以找到所有资源,包括给定层次结构中的类,甚至可以根据需要过滤它们。然后,您可以使用AbstractTypeHierarchyTraversingFilterAnnotationTypeFilterAssignableTypeFilter 的子级在类级别注释或它们实现的接口上过滤这些资源。

【讨论】:

    【解决方案5】:

    Java 1.6.0_24:

    public static File[] getPackageContent(String packageName) throws IOException{
        ArrayList<File> list = new ArrayList<File>();
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                                .getResources(packageName);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            File dir = new File(url.getFile());
            for (File f : dir.listFiles()) {
                list.add(f);
            }
        }
        return list.toArray(new File[]{});
    }
    

    此解决方案在EJB 环境中进行了测试。

    【讨论】:

      【解决方案6】:

      ScannotationReflections使用类路径扫描方式:

      Reflections reflections = new Reflections("my.package");
      Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);
      

      另一种方法是使用Java Pluggable Annotation Processing API 编写注释处理器,它将在编译时收集所有带注释的类并构建索引文件以供运行时使用。这个机制在ClassIndex库中实现:

      Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
      

      【讨论】:

        【解决方案7】:

        目前列出给定包中所有类的最强大的机制是ClassGraph,因为它处理widest possible array of classpath specification mechanisms,包括新的JPMS 模块系统。 (我是作者。)

        List<String> classNames;
        try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
                .enableClassInfo().scan()) {
            classNames = scanResult.getAllClasses().getNames();
        }
        

        【讨论】:

        • 几年后我才遇到这个。这是一个很棒的工具!它的工作速度比反射快得多,我喜欢它不调用静态初始化程序。正是我解决我们遇到的问题所需要的。
        【解决方案8】:

        eXtcos 看起来很有希望。想象一下,您想查找所有符合以下条件的类:

        1. 从“组件”类扩展,并存储它们
        2. 用“MyComponent”注释,并且
        3. 在“通用”包中。

        使用 eXtcos 就这么简单

        ClasspathScanner scanner = new ClasspathScanner();
        final Set<Class> classStore = new ArraySet<Class>();
        
        Set<Class> classes = scanner.getClasses(new ClassQuery() {
            protected void query() {
                select().
                from(“common”).
                andStore(thoseExtending(Component.class).into(classStore)).
                returning(allAnnotatedWith(MyComponent.class));
            }
        });
        

        【讨论】:

          【解决方案9】:
          1. Bill Burke 写了一篇(关于类扫描的 article 很好),然后他写了 Scannotation

          2. Hibernate已经写好了:

            • org.hibernate.ejb.packaging.Scanner
            • org.hibernate.ejb.packaging.NativeScanner
          3. CDI 可能会解决这个问题,但不知道 - 尚未完全调查

          .

          @Inject Instance< MyClass> x;
          ...
          x.iterator() 
          

          也适用于注解:

          abstract class MyAnnotationQualifier
          extends AnnotationLiteral<Entity> implements Entity {}
          

          【讨论】:

          • 文章链接已失效。
          【解决方案10】:

          据我所知,Java 反射 API 仍然可疑地缺少该功能。你可以通过这样做得到一个包对象:

          Package packageObj = Package.getPackage("my.package");
          

          但您可能已经注意到,这不会让您列出该包中的类。到目前为止,您必须采取一种更加面向文件系统的方法。

          我在this帖子中找到了一些示例实现

          我不能 100% 确定当您的类被隐藏在 JAR 文件中时这些方法是否有效,但我希望其中一种方法可以为您服务。

          我同意@skaffman...如果您有其他解决方法,我建议您这样做。

          【讨论】:

          • 这不是可疑的,它只是不能那样工作。类不“属于”包,它们具有对它们的引用。关联并不指向另一个方向。
          • @skaffman 非常有趣的一点。从来没有这样想过。所以只要我们沿着那条思想的轨迹徘徊,为什么关联不是双向的(现在这更多是出于我自己的好奇心)?
          【解决方案11】:

          我碰巧实现了它,它在大多数情况下都有效。由于篇幅较长,我把它放在file here中。

          这个想法是找到在大多数情况下可用的类源文件的位置(一个已知的例外是 JVM 类文件——据我测试)。如果代码在目录中,则扫描所有文件并仅发现类文件。如果代码在 JAR 文件中,请扫描所有条目。

          此方法只能在以下情况下使用:

          1. 你有一个类在你想发现的同一个包中,这个类叫做 SeedClass。例如,如果要列出 'java.io' 中的所有类,则种子类可能是 java.io.File

          2. 您的类位于目录或 JAR 文件中,它具有源文件信息(不是源代码文件,而只是源文件)。据我尝试,除了 JVM 类(这些类随 JVM)外,它几乎 100% 工作。

          3. 您的程序必须有权访问这些类的 ProtectionDomain。如果你的程序是本地加载的,应该没有问题。

          我只是为我的日常使用测试了该程序,所以它可能仍然存在问题。

          我希望这会有所帮助。

          【讨论】:

          • 这似乎是一个非常有趣的东西!我会尝试使用它。如果我发现对我有帮助,我可以在开源项目中使用您的代码吗?
          • 我也准备开源它。所以继续吧:D
          • @NawaMan:你最后开源了吗?如果是这样:我们在哪里可以找到最新版本?谢谢!
          【解决方案12】:

          这是另一个选项,对上面/下面的另一个答案稍作修改:

          Reflections reflections = new Reflections("com.example.project.package", 
              new SubTypesScanner(false));
          Set<Class<? extends Object>> allClasses = 
              reflections.getSubTypesOf(Object.class);
          

          【讨论】:

            【解决方案13】:

            当小程序很常见时,类路径上可能有一个 URL。当类加载器需要一个类时,它会搜索类路径上的所有位置,包括 http 资源。因为您可以在类路径中包含诸如 URL 和目录之类的东西,所以没有简单的方法来获得明确的类列表。

            但是,您可以非常接近。一些 Spring 库现在正在这样做。您可以在类路径中获取所有 jar,然后像文件一样打开它们。然后,您可以获取此文件列表,并创建一个包含您的类的数据结构。

            【讨论】:

              【解决方案14】:

              使用依赖maven:

              groupId: net.sf.extcos
              artifactId: extcos
              version: 0.4b
              

              然后使用此代码:

              ComponentScanner scanner = new ComponentScanner();
                      Set classes = scanner.getClasses(new ComponentQuery() {
                          @Override
                          protected void query() {
                              select().from("com.leyton").returning(allExtending(DynamicForm.class));
                          }
                      });
              

              【讨论】:

                【解决方案15】:

                Brent - 关联是一种方式的原因与 CLASSPATH 的任何组件上的任何类都可以在任何包中声明自己(java/javax 除外)这一事实有关。因此,在给定的“包”中没有所有类的映射,因为没有人知道也不能知道。您明天可以更新 jar 文件并删除或添加类。这就像试图在世界上所有国家/地区获得所有名为 John/Jon/Johan 的人的名单 - 我们都不是无所不知的,因此我们都不会有正确的答案。

                【讨论】:

                • 不错的哲学答案,但是 CDI bean 扫描是如何工作的呢?或者 Hibernate 如何扫描 @Entities?
                猜你喜欢
                • 1970-01-01
                • 2011-03-14
                • 2018-05-04
                • 2010-12-02
                • 2010-11-30
                • 1970-01-01
                • 2010-09-18
                • 2018-10-14
                相关资源
                最近更新 更多