В статье приведен небольшой обзор трех инструментов для поиска аннотированных классов в java проекте.


image


Тренировочные кошки
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}

@MyAnnotation
public class Bar {}

@MyAnnotation
public class Foo {}

Spring


В Spring для этих целей служит ClassPathScanningCandidateComponentProvider.


Особенность: лезет в ClassPath, ищет классы которые удовлетворяют заданным условиям

Дополнительные возможности

имеет множество других фильтров (для типа, для методов и т.д.)


Пример


@Benchmark
public void spring() {
   ClassPathScanningCandidateComponentProvider scanner = 
                        new ClassPathScanningCandidateComponentProvider(false);
   scanner.addIncludeFilter(new AnnotationTypeFilter(MyAnnotation.class));

   List<String> collect = scanner
      .findCandidateComponents("edu.pqdn.scanner")
      .stream()
      .map(BeanDefinition::getBeanClassName)
      .filter(Objects::nonNull)
      .collect(Collectors.toList());

   assertEquals(collect.size(), 2);
   assertTrue(collect.contains("edu.pqdn.scanner.test.Bar"));
   assertTrue(collect.contains("edu.pqdn.scanner.test.Foo"));
}

Reflections


Особенность: лезет в ClassPath, ищет классы которые удовлетворяют заданным условиям

Дополнительные возможности
  • get all subtypes of some type
  • get all types/members annotated with some annotation
  • get all resources matching a regular expression
  • get all methods with specific signature including parameters, parameter annotations and return type

dependency
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.11</version>
</dependency>

Пример


@Benchmark
public void reflection() {
   Reflections reflections = new Reflections("edu.pqdn.scanner");
   Set<Class<?>> set = reflections.getTypesAnnotatedWith(MyAnnotation.class);

   List<String> collect = set.stream()
      .map(Class::getCanonicalName)
      .collect(Collectors.toList());

   assertEquals(collect.size(), 2);
   assertTrue(collect.contains("edu.pqdn.scanner.test.Bar"));
   assertTrue(collect.contains("edu.pqdn.scanner.test.Foo"));
}

classindex


Особенность: НЕ лезет в ClassPath, вместо это классы индексируются на этапе компиляции

dependency
<dependency>
    <groupId>org.atteo.classindex</groupId>
    <artifactId>classindex</artifactId>
    <version>3.4</version>
</dependency>

Тренировочные кошки
@IndexMyAnnotated
public @interface IndexerAnnotation {}

@IndexerMyAnnotation
public class Bar {}

@IndexerMyAnnotation
public class Foo {}

Пример


@Benchmark
public void indexer() {
   Iterable<Class<?>> iterable = ClassIndex.getAnnotated(IndexerMyAnnotation.class);

   List<String> list = StreamSupport.stream(iterable.spliterator(), false)
      .map(aClass -> aClass.getCanonicalName())
      .collect(Collectors.toList());

   assertEquals(list.size(), 2);
   assertTrue(list.contains("edu.pqdn.scanner.classindexer.test.Bar"));
   assertTrue(list.contains("edu.pqdn.scanner.classindexer.test.Foo"));
}

JMH


Benchmark                    Mode  Cnt  Score   Error  Units
ScannerBenchmark.indexer     avgt   50  0,100 ? 0,001  ms/op
ScannerBenchmark.reflection  avgt   50  5,157 ? 0,047  ms/op
ScannerBenchmark.spring      avgt   50  4,706 ? 0,294  ms/op

Заключение


Как видно indexer самый производительный инструмент, однако, аннотации по которым производится поиск должны иметь стереотип @IndexAnnotated.


Другие два инструмента работают заметно медленнее, однако для их работы никакого шаманства с кодом производить не надо. Недостаток полностью нивелируется, если поиск необходим только на старте приложения

Комментарии (3)


  1. gltrinix
    26.09.2018 19:04

    Рассматриваются только фреймворки? Можно же и ручками отсканировать пути в ClassLoader, как описано здесь. Или написать свою реализацию javax.annotation.processing.AbstractProcessor.


  1. APXEOLOG
    26.09.2018 19:36

    Советую посмотреть на https://github.com/classgraph/classgraph (в прошлом fast-classpath-scanner)


    ClassGraph (formerly FastClasspathScanner) is an uber-fast, ultra-lightweight classpath scanner, module scanner, and classfile annotation processor for Java, Scala, Kotlin and other JVM languages.

    Имеет отличный функционал, ищет классы не только в jar'ках, но и просто из папок (удобно если запускать проект напрямую из IDE)


    1. PqDn Автор
      27.09.2018 00:36

      Да, это хорошее дополнение