282

如何在整个类路径中搜索带注释的类?

我正在做一个库,我想让用户注释他们的类,所以当 Web 应用程序启动时,我需要扫描整个类路径以获取某些注释。

我正在考虑类似 Java EE 5 Web 服务或 EJB 的新功能。@WebService您使用or注释您的类,@EJB系统会在加载时找到这些类,以便可以远程访问它们。

4

13 回答 13

234

使用org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

从基础包扫描类路径的组件提供程序。然后它将排除和包含过滤器应用于结果类以查找候选者。

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
于 2009-09-12T15:11:38.583 回答
155

另一个解决方案是ronmamo 的 Reflections

快速复审:

  • 如果您使用的是 Spring,那么 Spring 解决方案就是您要走的路。否则这是一个很大的依赖。
  • 直接使用 ASM 有点麻烦。
  • 直接使用 Java Assist 也很笨拙。
  • Annovention 超轻巧且方便。还没有 Maven 集成。
  • 反射索引所有内容,然后超级快。
于 2010-08-02T21:08:47.923 回答
44

您可以使用ClassGraph查找具有任何给定注释的类,以及搜索其他感兴趣的标准,例如实现给定接口的类。(免责声明,我是 ClassGraph 的作者。)ClassGraph 可以为整个类图(所有类、注解、方法、方法参数和字段)在内存中,为类路径上的所有类,或在列入白名单的包,您可以根据需要查询该类图。ClassGraph 支持比任何其他扫描器更多的类路径规范机制和类加载器,并且还可以与新的 JPMS 模块系统无缝协作,因此如果您的代码基于 ClassGraph,您的代码将具有最大的可移植性。请参阅此处的 API。

于 2014-08-17T22:53:25.263 回答
27

如果您想要一个真正轻量级(无依赖项、简单 API、15 kb jar 文件)和非常快速annotation-detector的解决方案,请查看https://github.com/rmuller/infomas-asl

免责声明:我是作者。

于 2011-11-21T09:05:47.840 回答
20

您可以使用 Java Pluggable Annotation Processing API 编写注释处理器,该处理器将在编译过程中执行,并收集所有带注释的类并构建索引文件以供运行时使用。

这是进行带注释的类发现的最快方法,因为您不需要在运行时扫描类路径,这通常是非常慢的操作。此外,这种方法适用于任何类加载器,而不仅仅是运行时扫描程序通常支持的 URLClassLoaders。

上述机制已经在ClassIndex库中实现。

要使用它,请使用@IndexAnnotated元注释来注释您的自定义注释。这将在编译时创建一个索引文件: META-INF/annotations/com/test/YourCustomAnnotation 列出所有带注释的类。您可以通过执行以下命令在运行时访问索引:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
于 2011-12-27T08:31:28.297 回答
8

zapp有一个精彩的评论,包含在所有这些答案中:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
于 2019-12-08T19:38:49.010 回答
6

Spring有一个叫做AnnotatedTypeScanner类的东西。
这个类内部使用

ClassPathScanningCandidateComponentProvider

此类具有用于实际扫描类路径资源的代码。它通过使用运行时可用的类元数据来做到这一点。

可以简单地扩展此类或使用相同的类进行扫描。下面是构造函数定义。

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
于 2019-05-28T09:58:47.190 回答
5

是不是回答太晚了。我会说,最好通过ClassPathScanningCandidateComponentProviderScannotations 之类的库

但即使有人想用 classLoader 尝试一下,我自己也写了一些来打印包中类的注释:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

在配置文件中,您输入包名并将其解组为一个类。

于 2017-11-22T06:53:43.923 回答
2

Classloader API 没有“枚举”方法,因为类加载是一种“按需”活动——您的类路径中通常有数千个类,其中只有一小部分是需要的(rt.jar如今,仅此一项就是 48MB!)。

因此,即使您可以枚举所有类,这也将非常耗费时间和内存。

简单的方法是在安装文件(xml 或任何适合您的文件)中列出相关类;如果您想自动执行此操作,请将自己限制在一个 JAR 或一个类目录中。

于 2008-11-03T17:24:19.180 回答
2

使用 Spring,您也可以使用 AnnotationUtils 类编写以下内容。IE:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

有关更多详细信息和所有不同方法,请查看官方文档: https ://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

于 2015-10-12T23:41:55.623 回答
2

如果您也想发现接口,请使用Google Reflection 。

SpringClassPathScanningCandidateComponentProvider没有发现接口。

于 2018-12-29T19:13:24.703 回答
1

Google Reflections似乎比 Spring 快得多。找到解决此差异的此功能请求:http ://www.opensaga.org/jira/browse/OS-738

这是使用反射的原因,因为我的应用程序的启动时间在开发过程中非常重要。对于我的用例,反射似乎也很容易使用(查找接口的所有实现者)。

于 2012-07-14T18:37:20.040 回答
1

如果您正在寻找反射的替代方法,我想推荐Panda Utilities - AnnotationsScanner。这是一个基于反射库源代码的无 Guava(Guava 有 ~3MB,Panda Utilities 有 ~200kb)扫描仪。

它还专门用于基于未来的搜索。如果您想多次扫描包含的源,甚至提供一个 API,它允许某人扫描当前的类路径,AnnotationsScannerProcess缓存所有 fetched ClassFiles,所以它真的很快。

简单的AnnotationsScanner使用示例:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
于 2018-08-14T13:02:16.637 回答