Skip to content

Spring 包扫描机制

是什么

ClassPathScanningCandidateComponentProvider 是 Spring Framework 自带的「按包扫描候选类」工具,底层基于 ASM 解析字节码,比手写反射或者引入 org.reflections 都更轻量;与 Spring 容器同源,启动时序、ClassLoader 行为天然契合。

配套常用类:

作用
ClassPathScanningCandidateComponentProvider扫描入口,可设置 includeFilters / excludeFilters
AnnotationTypeFilter一个开箱即用的 filter,按注解筛
BeanDefinition扫描结果的载体,可通过 getBeanClassName() 拿到全限定类名
AnnotatedBeanDefinitionBeanDefinition 的子类型,附带注解元数据

为什么不是 spring-core 而是 spring-context?

ClassPathScanningCandidateComponentProvider 位于 org.springframework.context.annotation,属于 spring-context 包。所以 forge-ddlpom.xml 显式声明了 spring-context 依赖。

怎么用

java
ClassPathScanningCandidateComponentProvider provider =
        new ClassPathScanningCandidateComponentProvider(false);  // false = 不读取默认过滤器

provider.addIncludeFilter(new AnnotationTypeFilter(Model.class));

for (BeanDefinition bd : provider.findCandidateComponents("com.demo")) {
    String className = bd.getBeanClassName();
    Class<?> clazz = Class.forName(className);
    // do something
}

构造器 useDefaultFiltersfalse 是惯用法:默认 filters 会把 @Component 系也加进来,本框架只想要 @Model,所以关掉。

覆写 isCandidateComponent

默认实现要求被扫描的类是「非抽象、非接口、独立类」。如果想放开(比如允许抽象类),就匿名子类化覆写:

java
new ClassPathScanningCandidateComponentProvider(false) {
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition definition) {
        return definition.getMetadata().isIndependent();
    }
};

本框架怎么用

ClazzScanner#buildProvider 同时使用了「关默认 filter + 覆写 isCandidateComponent + Include filter + Exclude filter」四种手法:

java
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false) {
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition definition) {
        return definition.getMetadata().isIndependent()
                && !definition.getMetadata().isAbstract()
                && !definition.getMetadata().isInterface();
    }
};
provider.addIncludeFilter(new AnnotationTypeFilter(Model.class));
provider.addExcludeFilter((reader, factory) ->
        reader.getClassMetadata().getClassName().contains("$"));  // 排除内部类

之后 scan(...)Class.forName(className, false, ContextClassLoader) 把类加载进来,并用 clazz.isMemberClass() / isAnonymousClass() / isLocalClass() 再过一遍兜底,确保最终只剩“独立的、可实例化的、标了 @Model 的”普通类。

→ 详细见 《扫描机制》