【问题标题】:Scanning Java annotations at runtime在运行时扫描 Java 注释
【发布时间】:2010-09-20 12:33:42
【问题描述】:

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

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

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

【问题讨论】:

    标签: java annotations classloader


    【解决方案1】:

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

    【讨论】:

    • 感谢您的信息。您还知道如何扫描类路径以查找字段具有自定义注释的类吗?
    • @Javatar 使用 Java 的反射 API。 .class.getFields() 对于每个字段,调用 getAnnotation()
    • 注意:如果您在 Spring 应用程序中执行此操作,Spring 仍将根据 @Conditional 注释进行评估和操作。因此,如果一个类的 @Conditional 值返回 false,则 findCandidateComponents 不会返回它,即使它与扫描仪的过滤器匹配。这让我今天大吃一惊——我最终改用了下面 Jonathan 的解决方案。
    • @ArthurRonald 对不起,亚瑟。我的意思是 BeanDefinition 对象不提供直接获取类的方法。最接近的似乎是getBeanClassName,它返回一个完全限定的类名,但该方法的确切行为尚不清楚。此外,还不清楚该类是在哪个类加载器中找到的。
    • @Max 试试这个:Class&lt;?&gt; cl = Class.forName(beanDef.getBeanClassName());farenda.com/spring/find-annotated-classes
    【解决方案2】:

    另一个解决方案是ronmamo'sReflections

    快速回顾:

    • 如果您使用的是 Spring,Spring 解决方案是您的最佳选择。否则,这是一个很大的依赖关系。
    • 直接使用 ASM 有点麻烦。
    • 直接使用 Java Assist 也很笨拙。
    • Annovention 超轻巧方便。尚无 Maven 集成。
    • Reflections 为所有内容编制索引,然后速度非常快。

    【讨论】:

    • new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)。 c'est tout。
    • 我需要指定一个包名吗?通配符?类路径中的所有类是什么?
    • 注意它在 Java 9 支持方面仍然没有进展:github.com/ronmamo/reflections/issues/186
    • org.reflections 库在 java 13 下无法正常工作(也许更早)。第一次调用它似乎没问题。随后的实例化和使用失败,说明未配置搜索 url。
    • Google 反映的链接无效。这是一个与 Google 无关的库。
    【解决方案3】:

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

    【讨论】:

    • 这需要 Java 8 才能运行吗?
    • 更新到使用Java7,没问题。只需删除注释并将函数转换为使用匿名内部类。我喜欢 1 文件样式。代码很干净,所以即使它不支持我想要的一些东西(同时类+注释)我认为添加起来非常容易。做得好!如果有人无法为 v7 进行修改,他们可能应该使用Reflections。此外,如果您使用的是 guava/etc 并且想要更改集合,那么简单。里面也很棒。
    • @Alexandros 谢谢,你应该看看 ClassGraph,它比 FastClasspathScanner 有了显着改进。
    • @AndrewBacker ClassGraph(FastClasspathScanner 的新版本)通过过滤器或集合操作完全支持布尔操作。在此处查看代码示例:github.com/classgraph/classgraph/wiki/Code-examples
    • @Luke Hutchison 已经在使用 ClassGraph。帮助我迁移到 Java 10。非常有用的库。
    【解决方案4】:

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

    免责声明:我是作者。

    【讨论】:

      【解决方案5】:

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

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

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

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

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

      【讨论】:

      • 在我的情况下它不起作用。我正在服务器上运行,java 11,Tomcat 9.0
      【解决方案6】:

      zapp 的精彩评论融入了所有这些答案:

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

          现在回答太晚了。 我会说,最好通过 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;
          
          }
          }
          

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

          【讨论】:

            【解决方案9】:

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

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

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

            【讨论】:

              【解决方案10】:

              使用 Spring,您也可以使用 AnnotationUtils 类编写以下代码。即:

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

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

              【讨论】:

              • 好主意,但是如果您将 null 值作为第二个参数(它定义了类,Spring 将在其中扫描使用 Annotation 的类),您将始终根据实现返回null
              【解决方案11】:

              Google Reflection 如果你也想发现接口。

              Spring ClassPathScanningCandidateComponentProvider 没有发现接口。

              【讨论】:

                【解决方案12】:

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

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

                【讨论】:

                • 如果您查看 JIRA 问题,那里的 cmets 由于稳定性问题而离开了 Reflections。
                【解决方案13】:

                如果您正在寻找reflections 的替代品,我想推荐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);
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2021-08-10
                  • 2015-02-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多