Tools for searching annotated classes in Java
- Tutorial
The article provides a small overview of the three tools for searching annotated classes in a java project.
Training cats
@Retention(RetentionPolicy.RUNTIME)
public@interface MyAnnotation {}
@MyAnnotationpublicclassBar{}
@MyAnnotationpublicclassFoo{}
Spring
In Spring, ClassPathScanningCandidateComponentProvider serves for this purpose.
Feature: climbs into ClassPath, looking for classes that meet specified conditions
Additional features
has many other filters (for type, for methods, etc.)
Example
@Benchmarkpublicvoidspring(){
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
Feature: climbs into ClassPath, looking for classes that meet specified conditions
Additional features
- get all subtypes of some type
- get all types / members annotated with some annotation
- get all resources matching a regular expression
- parameters, parameter returns and return type
dependency
<dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.9.11</version></dependency>
Example
@Benchmarkpublicvoidreflection(){
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
Feature: Does not climb in ClassPath, instead of this classes are indexed at the compilation stage
dependency
<dependency><groupId>org.atteo.classindex</groupId><artifactId>classindex</artifactId><version>3.4</version></dependency>
Training cats
@IndexMyAnnotatedpublic@interface IndexerAnnotation {}
@IndexerMyAnnotationpublicclassBar{}
@IndexerMyAnnotationpublicclassFoo{}
Example
@Benchmarkpublicvoidindexer(){
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 ErrorUnits
ScannerBenchmark.indexer avgt 500,100 ? 0,001 ms/op
ScannerBenchmark.reflection avgt 505,157 ? 0,047 ms/op
ScannerBenchmark.spring avgt 504,706 ? 0,294 ms/op
Conclusion
As you can see, the indexer is the most productive tool, however, the annotations for which the search is performed must have the stereotype @IndexAnnotated.
The other two tools are working much slower, but for their work no shamanism with code is needed. The disadvantage is completely leveled out if the search is needed only at the start of the application.