在运行时扫描Java批注[关闭]


253

在整个类路径中搜索带注释的类的最佳方法是什么?

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

您知道执行此操作的库或Java工具吗?

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

Answers:


210

使用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());

5
谢谢你给的信息。您还知道如何在类路径中扫描具有自定义注释的类吗?
Javatar 2012年

6
@Javatar使用Java的反射API。<YOUR_CLASS> .class.getFields()对于每个字段,调用getAnnotation(<YOUR_ANNOTATION>)
Arthur Ronald

1
注意:如果您在Spring应用程序中执行此操作,Spring仍将基于@Conditional注释进行评估并采取行动。因此,如果一个类的@Conditional值返回false findCandidateComponents,即使它与扫描器的过滤器匹配,也不会由返回。今天,这让我感到震惊-我最终在下面使用了Jonathan的解决方案。
亚当·伯利

1
@ArthurRonald对不起,Arthur。我的意思是该BeanDefinition对象没有提供直接获取类的方法。最接近的东西似乎是getBeanClassName返回完全限定的类名,但是此方法的确切行为尚不清楚。此外,还不清楚该类是在哪个类加载器中找到的
Max

3
@Max尝试一下:Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins

149

另一个解决方案是Google的思考

快速复审:

  • 如果您使用的是Spring,则应采用Spring解决方案。否则这是一个很大的依赖。
  • 直接使用ASM有点麻烦。
  • 直接使用Java Assist也很麻烦。
  • Annovention超轻便且方便。尚未进行Maven集成。
  • Google的思考吸引了Google的收藏。索引所有内容,然后超级快。

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)。吹牛。
zapp 2013年

4
我需要指定包装名称吗?通配符?对于classpath中的所有类呢?
Sunnyday

1
请注意,它在Java 9支持方面仍然没有进展:github.com/ronmamo/reflections/issues/186
Vadzim

org.reflections库无法在Java 13下正常运行(可能也更早)。第一次调用它似乎还可以。随后的实例化和使用失败,表明未配置搜索URL。
Evvo

44

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


1
这需要Java 8才能运行吗?
大卫·乔治

1
更新为使用Java7,没问题。只需删除注释,然后将函数转换为使用匿名内部类即可。我喜欢1文件样式。该代码很干净,所以即使它不支持我想要的一些东西(同时提供类+注释),我也认为这很容易添加。做得好!如果某人无法完成修改v7的工作,则应该选择Reflections。另外,如果您使用的是番石榴/ etc并想更改其集合,则很容易。里面的评论也很棒。
Andrew Backer 2014年

2
@Alexandros谢谢,您应该检查ClassGraph,它比FastClasspathScanner有了显着改进。
卢克·哈奇森

2
@AndrewBacker ClassGraph(FastClasspathScanner的新版本)完全支持通过过滤器或设置操作进行布尔运算。在此处查看代码示例:github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison

1
@Luke Hutchison已经使用ClassGraph。帮助我迁移到Java10。非常有用的库。
亚历山德罗斯


20

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

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

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

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

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

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;

}
}

然后在config File中,将包名称放入并解组到class中。


3

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

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

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


4
不错的主意,但是如果您将一个null值作为第二个参数(定义了类,Spring将在该类中继承层次结构,Spring将在其中扫描使用Annotation的类)null,那么根据实现,总会得到回报。
jonashackt

3

zapp有一个很棒的评论,它涵盖了所有这些答案:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

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

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

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



1

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

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


1
如果您查看JIRA问题,则由于稳定性问题,有评论认为它们脱离了Reflections。
Wim Deblauwe 2015年

1

如果您正在寻找反射的替代方法,我建议您使用Panda Utilities-AnnotationsScanner。它是基于反射库源代码的无Guava程序(Guava程序约为3MB,Panda Utilities程序约为200kb)。

它还专用于基于未来的搜索。如果您想扫描多次包含的源代码,或者甚至提供一个API,它允许某人扫描当前的类路径,将AnnotationsScannerProcess所有提取的内容都缓存起来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);

1

春天有一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;
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.