You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用ClassPathScanningCandidateComponentProvider查找带指定注解的接口?

问题描述

因org.reflections:reflections库存在严重bug,计划用org.springframework:spring-context的ClassPathScanningCandidateComponentProvider替代。需要实现以下功能:查找指定包下所有带@Path注解的类、接口,以及这些类/接口的子类/实现类。

原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);

已实现子类/实现类的查找,但当前代码无法找到带@Path注解的接口,只能找到带注解的类,以及继承/实现带注解类/接口的类。现有代码片段:

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() );
}
解决方案

核心问题

默认的ClassPathScanningCandidateComponentProvider在不配置自定义过滤器时,只会扫描类,不会包含接口。需要自定义TypeFilter来匹配带@Path注解的类和接口,再结合子类/实现类的扫描,最终合并结果。

实现步骤

  1. 创建支持扫描注解接口的扫描器,自定义TypeFilter检查类/接口上的@Path注解
  2. 扫描所有带注解的类和接口
  3. 扫描这些类/接口的子类/实现类
  4. 合并去重结果

完整代码示例

import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.TypeFilter;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;

public class ComponentScanner {

    private final Class<? extends Annotation> targetAnnotation;
    private final String basePackage;

    public ComponentScanner(Class<? extends Annotation> targetAnnotation, String basePackage) {
        this.targetAnnotation = targetAnnotation;
        this.basePackage = basePackage;
    }

    public Set<Class<?>> scanAllMatchingTypes() {
        // 1. 扫描所有带目标注解的类和接口
        Set<Class<?>> annotatedTypes = scanAnnotatedTypes();
        // 2. 扫描这些类型的子类/实现类
        Set<Class<?>> subTypes = scanSubTypes(annotatedTypes);
        // 3. 合并去重
        Set<Class<?>> result = new HashSet<>(annotatedTypes);
        result.addAll(subTypes);
        return Collections.unmodifiableSet(result);
    }

    private Set<Class<?>> scanAnnotatedTypes() {
        // 创建不使用默认过滤器的扫描器
        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
        
        // 添加自定义过滤器:匹配带目标注解的类或接口
        TypeFilter annotationFilter = (metadataReader, metadataReaderFactory) -> 
            metadataReader.getAnnotationMetadata().hasAnnotation(targetAnnotation.getName())
            // 若需支持注解继承(如注解本身带@Inherited),可添加下面的判断
            // || metadataReader.getAnnotationMetadata().hasMetaAnnotation(targetAnnotation.getName())
            ;
        
        scanner.addIncludeFilter(annotationFilter);
        
        return scanner.findCandidateComponents(basePackage)
                .stream()
                .map(beanDef -> {
                    try {
                        return Class.forName(beanDef.getBeanClassName());
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("Failed to load class: " + beanDef.getBeanClassName(), e);
                    }
                })
                .collect(Collectors.toUnmodifiableSet());
    }

    private Set<Class<?>> scanSubTypes(Set<Class<?>> parentTypes) {
        return parentTypes.stream()
                .flatMap(parentType -> {
                    ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
                    // 添加过滤器:匹配继承/实现parentType的类(含接口实现类),排除父类型本身
                    scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> {
                        try {
                            Class<?> currentClass = Class.forName(metadataReader.getClassMetadata().getClassName());
                            return parentType.isAssignableFrom(currentClass) && !parentType.equals(currentClass);
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException("Failed to load class: " + metadataReader.getClassMetadata().getClassName(), e);
                        }
                    });
                    return scanner.findCandidateComponents(basePackage).stream();
                })
                .map(beanDef -> {
                    try {
                        return Class.forName(beanDef.getBeanClassName());
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException("Failed to load class: " + beanDef.getBeanClassName(), e);
                    }
                })
                .collect(Collectors.toUnmodifiableSet());
    }

    // 使用示例
    public static void main(String[] args) {
        ComponentScanner scanner = new ComponentScanner(Path.class, "com.example");
        Set<Class<?>> allMatchingTypes = scanner.scanAllMatchingTypes();
        allMatchingTypes.forEach(type -> System.out.println("Found type: " + type.getName()));
    }
}

关键说明

  • 自定义TypeFilter:通过metadataReader.getAnnotationMetadata()可获取类/接口的注解信息,不管是类还是接口都能被检测到。
  • 子类/实现类扫描:使用isAssignableFrom判断,同时支持类继承和接口实现,排除父类/接口本身避免重复。
  • 结果合并:将带注解的类型和它们的子类/实现类合并,实现与原Reflections一致的效果。

内容的提问来源于stack exchange,提问作者Mariusz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 09:36:20