hexon
发布于 2025-12-17 / 11 阅读
0

ComponentScan注解属性详解

本文是《BeanDefinition扫描过程源码解析》解析的前置补充文章。

一、基本概述

@ComponentScan 是 Spring 框架中用于自动扫描和注册 Bean 的核心注解。它告诉 Spring 容器在哪些包路径下查找带有 @Component@Service@Repository@Controller 等注解的类,并将它们注册为 Spring Bean。

二、属性详解

1. 基础扫描路径配置

value / basePackages

// 两种写法等价
@ComponentScan("com.example")
@ComponentScan(basePackages = "com.example")
@ComponentScan(basePackages = {"com.example.dao", "com.example.service"})
  • 类型String[]

  • 作用:指定要扫描的基础包路径

  • 注意valuebasePackages 的别名,两者功能完全相同

  • 默认值:空数组,如果不指定,则扫描声明该注解的类所在的包及其子包

basePackageClasses

@ComponentScan(basePackageClasses = {UserService.class, UserDao.class})
  • 类型Class<?>[]

  • 作用:通过类来间接指定包路径,Spring 会扫描这些类所在的包

  • 优点:类型安全,重构时IDE会自动更新

  • 典型用法:在包中创建一个标记接口(Marker Interface)

// 创建标记接口
package com.example.config;
public interface ScanMarker {}
​
// 使用标记接口
@ComponentScan(basePackageClasses = ScanMarker.class)
// 这样会扫描 com.example.config 包及其子包

注意:只是说扫描,并没有说一定是Bean,Bean最终默认还是要标注@Component或者其衍生注解。

2. 包含/排除过滤器

includeFilters

使用语法如下:

@ComponentScan(
    includeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = MyCustomAnnotation.class),
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyBaseClass.class),
        @Filter(type = FilterType.ASPECTJ, pattern = "com.example..*Service"),
        @Filter(type = FilterType.REGEX, pattern = ".*Repository"),
        @Filter(type = FilterType.CUSTOM, classes = MyCustomFilter.class)
    }
)
  • 类型Filter[]

  • 作用:指定哪些组件应该被包含进来(即使它们没有 @Component 等注解)

  • useDefaultFilters 的关系:当 useDefaultFilters=true(默认)时,包含过滤器额外添加扫描目标

下面分别演示各种情况:

① 指定注解

@ComponentScan(
        includeFilters = {
                @Filter(type = FilterType.ANNOTATION, classes = MyCustomAnnotation.class),
        }, useDefaultFilters = false
)
public class MyConfig {
}
//@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME) // 注意:这个是必须要指定的,这样才能保证编译后的class文件有注解信息,Spring才能解析到
public @interface MyCustomAnnotation {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        applicationContext.getBean("userService");
        applicationContext.getBean("userDao");
        applicationContext.getBean("userController");
    }
}

这里我把useDefaultFilters = false,并且三个业务类上只有@MyCustomAnnotation注解,也可以获取Bean。

② 指定类型

@ComponentScan(
        useDefaultFilters = false,
        includeFilters = {
                @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UserBase.class)
        }
)
public class MyConfig {
}
public class UserBase {
}
public class UserService extends UserBase {
}
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
        applicationContext.getBean("userService");
        applicationContext.getBean("userBase");
    }
}

这里我把useDefaultFilters = false,使用ASSIGNABLE_TYPE过滤器后基类与子类都没有加@Component注解,但都可以获取到Bean。

excludeFilters

使用语法如下:

@ComponentScan(
    excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = Controller.class),
        @Filter(type = FilterType.REGEX, pattern = ".*Test.*")
    }
)
  • 类型Filter[]

  • 作用:排除特定的组件,即使它们匹配了包含条件或有 @Component 注解

一般排除的优先级会高于包含

useDefaultFilters

@ComponentScan(useDefaultFilters = false)
  • 类型boolean

  • 作用:是否启用默认的过滤器

  • 默认值true

  • 详细说明

    • true(默认):会自动检测带有 @Component@Repository@Service@Controller@Configuration 的类

    • false:关闭默认检测,只扫描 includeFilters 指定的组件

3. 作用域相关

scopedProxy

语法如下:

@ComponentScan(scopedProxy = ScopedProxyMode.INTERFACES)
@ComponentScan(scopedProxy = ScopedProxyMode.TARGET_CLASS)
@ComponentScan(scopedProxy = ScopedProxyMode.NO)
  • 类型ScopedProxyMode

  • 作用:为作用域 Bean 生成代理的方式

  • 可选值

    • ScopedProxyMode.NO(默认):不创建代理

    • ScopedProxyMode.INTERFACES:使用 JDK 动态代理(基于接口)

    • ScopedProxyMode.TARGET_CLASS:使用 CGLIB 代理(基于类)

  • 典型场景:在单例 Bean 中注入 request/session 作用域的 Bean 时需要使用

这里只是先了解一下这个属性,后面会有地方详细学习的:单例 Bean 注入 request Bean 会失败,因为 request Bean 依赖 Web 请求上下文,而单例在容器启动时就创建了。Spring 的解决办法:使用代理(Scoped Proxy)来延迟真正对象的获取

scopeResolver

语法:

@ComponentScan(scopeResolver = MyCustomScopeResolver.class)
  • 类型Class<? extends ScopeMetadataResolver>

  • 作用:自定义作用域解析策略

  • 默认值AnnotationScopeMetadataResolver.class

scopedProxyMode

  • 类型ScopedProxyMode

  • 注意:Spring 4.3+ 已废弃,建议使用 scopedProxy 属性

在 Spring Framework 6.x(从 6.0 开始)中,scopedProxyMode 已经被彻底移除,不再出现在源码里。

4. Bean 命名策略

nameGenerator

语法:

@ComponentScan(nameGenerator = MyBeanNameGenerator.class)
  • 类型Class<? extends BeanNameGenerator>

  • 作用:自定义 Bean 名称生成器

  • 默认值AnnotationBeanNameGenerator.class

  • 自定义示例

public class CustomNameGenerator extends AnnotationBeanNameGenerator {
    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        // 自定义命名逻辑
        return "custom_" + super.generateBeanName(definition, registry);
    }
}

resourcePattern

语法:

@ComponentScan(resourcePattern = "**/*.class")
  • 类型String

  • 作用:控制扫描的资源模式

  • 默认值"**/*.class"(扫描所有 class 文件)

  • 修改示例"**/*Controller.class" 只扫描 Controller 类

5. 懒加载控制

lazyInit

语法:

@ComponentScan(lazyInit = true)
  • 类型boolean

  • 作用:是否懒初始化扫描到的 Bean

  • 默认值false

  • 注意:这个设置会被 @Lazy 注解覆盖

相当于是一个全局的控制,但是单个Bean定义可以覆盖

三、FilterType 详解

FilterType 的 5 种类型

1. ANNOTATION(注解过滤)

@Filter(type = FilterType.ANNOTATION, classes = {Service.class, Repository.class})
  • 最常用:按注解类型过滤

2. ASSIGNABLE_TYPE(类型过滤)

@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BaseService.class, BaseDao.class})s
  • 扫描指定类及其子类

3. ASPECTJ(AspectJ 表达式)

@Filter(type = FilterType.ASPECTJ, pattern = "com.example.service.*Service")

4. REGEX(正则表达式)

@Filter(type = FilterType.REGEX, pattern = ".*Controller")

5. CUSTOM(自定义过滤)

@Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)
  • 需要实现 TypeFilter 接口:

public class MyTypeFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, 
                         MetadataReaderFactory metadataReaderFactory) {
        // 自定义匹配逻辑
        return metadataReader.getClassMetadata().getClassName().contains("Service");
    }
}