Инструменты для поиска аннотированных классов в Java

В статье приведен небольшой обзор трех инструментов для поиска аннотированных классов в 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 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"));
}


Особенность: лезет в 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

    org.reflections
    reflections
    0.9.11


Пример

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

   List 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"));
}


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


dependency

    org.atteo.classindex
    classindex
    3.4


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

@IndexerMyAnnotation
public class Bar {}

@IndexerMyAnnotation
public class Foo {}


Пример

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

   List 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.

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

© Habrahabr.ru