Home > Software design >  How to find interfaces with specified annotation using ClassPathScanningCandidateComponentProvider?
How to find interfaces with specified annotation using ClassPathScanningCandidateComponentProvider?

Time:08-06

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?

CodePudding user response:

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"
]
}
  • Related