Issue
I try to replace org.reflections:reflections library by org.springframework:spring-context and ClassPathScanningCandidateComponentProvider
because reflections library has a bug that is critical for me.
I want to find all classes, interfaces and subclasses that contain specific annotation. It was easy in reflections:
var subTypeScanner = new SubTypesScanner( true );
var typeAnnotationScanner = new TypeAnnotationsScanner();
var configBuilder = new ConfigurationBuilder().setUrls( ClasspathHelper.forPackage( "com.example" ) )
.setScanners( subTypeScanner, typeAnnotationScanner );
Reflections reflections= new Reflections( configBuilder );
Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(Path.class);
Unfortunately it is not easy for me using ClassPathScanningCandidateComponentProvider
. I have made workaround to find subclasses. First I find set of classes and after that I search for subclasses separately for each class:
protected Set< Class< ? > > findClasses( ClassPathScanningCandidateComponentProvider aScanner )
{
return aScanner.findCandidateComponents( "com.example" )
.stream()
.map( BeanDefinition::getBeanClassName )
.map( e -> {
try
{
return Class.forName( e );
}
catch( ClassNotFoundException aE )
{
throw new RuntimeException( aE );
}
} )
.collect( Collectors.toUnmodifiableSet() );
}
protected Set< Class< ? > > findSubClasses( Set< Class< ? > > aClasses )
{
return aClasses.stream()
.map( aClass -> {
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider( false );
scanner.addIncludeFilter( new AssignableTypeFilter( aClass ) );
return scanner.findCandidateComponents( "com.example" );
} )
.flatMap(Collection::stream)
.map( BeanDefinition::getBeanClassName )
.map( e -> {
try
{
return Class.forName( e );
}
catch( ClassNotFoundException aE )
{
throw new RuntimeException( aE );
}
} )
.collect( Collectors.toUnmodifiableSet() );
}
Unfortunately I do not know how to find interfaces with annotation. Following code does not finds interfaces. It only finds classes that has Path
annotation or extends class or implements interface that has Path
annotation.
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider( false );
scanner.addIncludeFilter( new AnnotationTypeFilter( Path.class, true, true ) );
Set<Class<?>> classes = findClasses(scanner);
//findSubClasses(findClasses( scanner ))
How to find interfaces with Path
annotation?
Solution
The current implementation is also clear as mentioned on method level docs
The default implementation checks whether the class is not an interface and not dependent on an enclosing class.
As per this github issue we can fix this by overridden behaviour of isCandidateComponent()
method
Here is the sample reference working code from the github issue
provider = new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isAbstract();
}
};
Annotation
package in.silentsudo.classpathannotationscanner.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyCustomAnnotation {
}
My Simple interface
package in.silentsudo.classpathannotationscanner;
import in.silentsudo.classpathannotationscanner.annotation.MyCustomAnnotation;
@MyCustomAnnotation
public interface SimpleInterface {
}
Sample test code
package in.silentsudo.classpathannotationscanner;
import in.silentsudo.classpathannotationscanner.annotation.MyCustomAnnotation;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@RestController
@RequestMapping("/list")
public class SampleController {
private final ClassPathScanningCandidateComponentProvider provider;
SampleController() {
provider = new ClassPathScanningCandidateComponentProvider(false) {
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return super.isCandidateComponent(beanDefinition) || beanDefinition.getMetadata().isAbstract();
}
};
provider.addIncludeFilter(new AnnotationTypeFilter(MyCustomAnnotation.class, true, true));
}
@GetMapping
public Map<String, Object> list() throws Exception {
return Map.of("mycustomannotationsmarkedinterafce", getData());
}
public List<String> getData() throws Exception {
final Set<BeanDefinition> classes = provider.findCandidateComponents("in.silentsudo.classpathannotationscanner");
List<String> names = new ArrayList<>();
for (BeanDefinition bean : classes) {
Class<?> clazz = Class.forName(bean.getBeanClassName());
names.add(clazz.getName());
}
return names;
}
}
This should give following response:
{
"mycustomannotationsmarkedinterafce": [
"in.silentsudo.classpathannotationscanner.SimpleInterface"
]
}
Answered By - silentsudo
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)