在 Spring 框架中,@ComponentScan
和 @ComponentScans
是用于组件扫描(Component Scanning)的核心注解,用于告诉 Spring 容器在哪些包路径下扫描带有特定注解(如 @Component
、@Service
、@Controller
等)的类,并将这些类注册为 Spring Bean。其中,@ComponentScan
用于定义单个扫描配置,而 @ComponentScans
是其复数形式,允许定义多个独立的扫描配置(适用于需要多路径、多过滤规则的复杂场景)。
一、核心概念与区别
注解 | 作用 | 适用场景 |
---|---|---|
@ComponentScan | 定义单个组件扫描配置(扫描路径、过滤规则等)。 | 单一扫描路径或简单过滤规则(如仅扫描当前包及其子包)。 |
@ComponentScans | 定义多个独立的组件扫描配置(通过 value 属性指定多个 @ComponentScan )。 | 复杂扫描需求(如多模块扫描、不同路径使用不同过滤规则)。 |
二、@ComponentScan
源码解析
@ComponentScan
位于 org.springframework.context.annotation
包中,其源码定义如下(简化版):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ComponentScan {
/**
* 扫描的基础包路径(默认当前类所在包)
*/
String[] basePackages() default {};
/**
* 扫描的基础类(默认扫描所有 @Component 及其派生注解)
*/
Class<?>[] basePackageClasses() default {};
/**
* 包含过滤器(仅扫描符合条件的类)
*/
Filter[] includeFilters() default {};
/**
* 排除过滤器(排除不符合条件的类)
*/
Filter[] excludeFilters() default {};
/**
* 是否启用懒加载(默认 false)
*/
boolean lazyInit() default false;
/**
* 作用域解析策略(默认使用注解定义的作用域)
*/
ScopeMetadataResolver scopeMetadataResolver() default AnnotationScopeMetadataResolver.class;
/**
* Bean 名称生成器(默认使用注解定义的名称)
*/
BeanNameGenerator beanNameGenerator() default AnnotationBeanNameGenerator.class;
/**
* 资源加载器(默认使用上下文资源加载器)
*/
ResourceLoader resourceLoader() default ResourceLoader.class;
/**
* 作用域别名(默认无)
*/
Scope[] scopes() default {};
/**
* 自定义扫描器(默认使用 ClassPathBeanDefinitionScanner)
*/
Class<? extends BeanDefinitionScanner>[] useDefaultFilters() default {};
// 内部接口:过滤器类型
enum FilterType {
ANNOTATION, // 基于注解过滤(如 @Controller)
ASSIGNABLE_TYPE, // 基于类型过滤(如 Number.class)
ASPECTJ, // 基于 AspectJ 表达式过滤(如 @within(com.example.Annotation))
REGEX, // 基于正则表达式过滤(如 .*Service$)
CUSTOM // 自定义过滤器(实现 TypeFilter 接口)
}
// 过滤器定义(简化)
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
Class<?>[] classes() default {};
String[] value() default {}; // 用于 ANNOTATION、REGEX 等类型的参数
}
}
关键属性说明:
-
basePackages
:指定扫描的基础包路径(如{"com.example.service", "com.example.dao"}
),Spring 会扫描这些包及其子包下的所有类。- 若未指定,默认扫描当前配置类所在包及其子包(这是 Spring Boot 自动配置的核心逻辑之一)。
-
basePackageClasses
:通过指定类(如UserService.class
)间接指定扫描路径(该类所在的包及其子包)。 -
includeFilters
:定义包含规则,仅扫描符合以下条件的类:ANNOTATION
:类被指定注解标记(如@Controller
)。ASSIGNABLE_TYPE
:类是某个类型的子类或实现类(如Number.class
)。ASPECTJ
:类匹配 AspectJ 表达式(如@within(com.example.Loggable)
)。REGEX
:类名匹配正则表达式(如.*Service$
)。CUSTOM
:自定义TypeFilter
实现类。
-
excludeFilters
:定义排除规则,排除符合以下条件的类(与includeFilters
互斥,优先级更高)。
三、@ComponentScans
源码解析
@ComponentScans
是 @ComponentScan
的复数形式,允许在一个类上定义多个独立的扫描配置。其源码定义如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ComponentScans {
/**
* 多个 @ComponentScan 配置
*/
ComponentScan[] value();
}
核心逻辑:
@ComponentScans
的value
属性接收一个@ComponentScan
数组,每个元素代表一个独立的扫描配置。- Spring 容器会依次处理每个
@ComponentScan
配置,合并或覆盖扫描结果(具体行为取决于过滤规则)。
四、工作流程与源码实现
Spring 处理 @ComponentScan
/@ComponentScans
的核心流程如下(简化):
1. 解析注解
在 Spring 容器启动时,ConfigurationClassParser
会解析主配置类(标注 @SpringBootApplication
的类)及其依赖的配置类,识别其中的 @ComponentScan
或 @ComponentScans
注解。
2. 生成扫描任务
对于每个 @ComponentScan
配置,Spring 会创建一个 ClassPathBeanDefinitionScanner
实例,用于执行实际的扫描操作。ClassPathBeanDefinitionScanner
是 Spring 扫描组件的核心工具类,负责:
- 遍历指定包路径下的所有类。
- 根据
includeFilters
和excludeFilters
过滤类。 - 为符合条件的类生成
BeanDefinition
(包含类名、作用域、依赖等信息)。
3. 注册 BeanDefinition
扫描生成的 BeanDefinition
会被注册到 BeanFactory
中(如 DefaultListableBeanFactory
),后续容器会根据这些定义实例化和管理 Bean。
4. 处理多扫描配置(@ComponentScans
)
若存在多个 @ComponentScan
配置(通过 @ComponentScans
定义),Spring 会依次执行每个扫描任务,最终将所有符合条件的 BeanDefinition
合并到 BeanFactory
中。
五、典型使用场景
1. 单一扫描路径(@ComponentScan
)
最常见的场景是主配置类标注 @ComponentScan
,扫描当前包及其子包下的组件(Spring Boot 默认行为):
@SpringBootApplication
@ComponentScan // 等价于 @ComponentScan(basePackages = {"com.example.demo"})(假设主类在 com.example.demo 包)
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. 多路径扫描(@ComponentScans
)
当项目模块化,需要扫描多个独立包路径时(如 service
、dao
、controller
分属不同模块),可使用 @ComponentScans
:
@SpringBootApplication
@ComponentScans({
@ComponentScan(basePackages = "com.example.service"), // 扫描 service 包
@ComponentScan(basePackages = "com.example.dao"), // 扫描 dao 包
@ComponentScan(basePackages = "com.example.controller") // 扫描 controller 包
})
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. 自定义过滤规则
通过 includeFilters
和 excludeFilters
控制扫描范围(例如仅扫描 @RestController
注解的类):
@Configuration
@ComponentScan(
basePackages = "com.example",
includeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION,
classes = RestController.class // 仅扫描 @RestController 注解的类
),
excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
classes = BaseController.class // 排除 BaseController 及其子类
)
)
public class AppConfig {
}
4. 懒加载配置
通过 lazyInit = true
开启懒加载(仅在需要时实例化 Bean):
@ComponentScan(lazyInit = true)
public class LazyConfig {
}
六、注意事项
-
扫描路径的优先级:
basePackages
和basePackageClasses
可同时使用,但basePackages
优先级更高(会覆盖basePackageClasses
的路径)。- 若未指定任何路径,Spring 会扫描当前配置类所在包及其子包(这是 Spring Boot 自动配置的关键)。
-
过滤规则的冲突:
includeFilters
和excludeFilters
互斥,若同时存在,excludeFilters
的规则优先级更高(即先包含再排除)。
-
性能影响:
- 扫描大量包或复杂过滤规则可能影响启动性能,建议通过
excludeFilters
排除不必要的类(如测试类、工具类)。
- 扫描大量包或复杂过滤规则可能影响启动性能,建议通过
-
Spring Boot 的默认扫描:
- Spring Boot 主配置类的
@SpringBootApplication
注解隐含了@ComponentScan
,默认扫描主类所在包及其子包。 - 若需扩展扫描路径,可通过
@ComponentScan
或@ComponentScans
显式指定,无需修改主类位置。
- Spring Boot 主配置类的
七、总结
@ComponentScan
和 @ComponentScans
是 Spring 组件扫描的核心注解,用于控制 Spring 容器扫描哪些包、哪些类,并将符合条件的类注册为 Bean。其中:
@ComponentScan
适用于单一扫描路径或简单过滤规则。@ComponentScans
适用于多路径、多过滤规则的复杂场景。
理解其源码和工作流程,有助于开发者灵活控制 Bean 的注册过程,解决组件未被扫描、错误扫描等常见问题,提升应用的灵活性和可维护性。