В статье приведен небольшой обзор трех инструментов для поиска аннотированных классов в java проекте.
@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>
<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>
<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)
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)
gltrinix
Рассматриваются только фреймворки? Можно же и ручками отсканировать пути в ClassLoader, как описано здесь. Или написать свою реализацию javax.annotation.processing.AbstractProcessor.