乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@ComponentScan 详解

本章定位:深入理解 Spring 的组件自动扫描机制。@ComponentScan 是连接"注解标注的类"与"容器中的 Bean"的桥梁,没有它,@Component、@Service、@Repository 等注解只是孤立的标记。


定义与作用

@ComponentScan 是 Spring 提供的组件扫描注解,用于配置 Spring 自动扫描并注册指定包路径下的 @Component 及其派生注解(@Service、@Repository、@Controller)标记的类。

解决的痛点:在没有 @ComponentScan 之前,每个 Bean 都需要在配置类中显式声明,即使它们已经标注了 @Component:

// 痛点:即使类上有 @Component,仍需在配置类中逐个注册
@Configuration
public class AppConfig {
    @Bean
    public StudentDao studentDao() {
        return new StudentDao();  // 冗余!StudentDao 上已有 @Repository
    }

    @Bean
    public StudentService studentService() {
        return new StudentService(studentDao());  // 冗余!
    }

    @Bean
    public EmailSender emailSender() {
        return new EmailSender();  // 冗余!
    }
}

@ComponentScan 让容器自动发现类路径下的候选组件,彻底消除冗余的 @Bean 声明:

// 解决后:一行注解,自动扫描所有组件
@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
    // 无需再手动声明 @Bean,StudentDao、StudentService、EmailSender 自动注册
}

适用位置与常用属性

适用位置

@ComponentScan 只能标注在类级别,通常与 @Configuration 配合使用。

@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
}

常用属性

属性类型默认值说明
value / basePackagesString[]{}指定扫描的包路径
basePackageClassesClass<?>[]{}指定扫描的基准类,Spring 会扫描该类所在的包
useDefaultFiltersbooleantrue是否启用默认过滤器(扫描 @Component 及其派生)
includeFiltersFilter[]{}自定义包含规则
excludeFiltersFilter[]{}自定义排除规则
lazyInitbooleanfalse是否延迟初始化扫描到的 Bean
nameGeneratorClass<? extends BeanNameGenerator>BeanNameGenerator.classBean 名称生成器
scopeResolverClass<? extends ScopeMetadataResolver>AnnotationScopeMetadataResolver.class作用域解析器

核心原理:组件扫描流程

@ComponentScan 触发后,Spring 执行以下扫描流程:

关键实现细节:

  1. ASM 扫描:Spring 使用 ASM 库直接读取 .class 文件的二进制内容,而非通过类加载器加载。这意味着扫描阶段即使类的依赖缺失,也不会报错。
  2. 递归扫描:扫描指定包及其所有子包。
  3. 默认过滤器:默认只识别标注了 @Component、@Service、@Repository、@Controller 的类。
  4. Bean 名称生成:默认使用 AnnotationBeanNameGenerator,将类名首字母小写。

完整示例:飞翔科技公司的模块化扫描

场景简述

飞翔科技的学生管理系统采用模块化结构:controller、service、dao、config 分别放在不同包下。后端开发小崔需要配置组件扫描,确保所有模块的 Bean 都被正确注册,同时排除某些不需要纳入容器的工具类。

操作前:逐个手动注册 Bean

// 操作前:AppConfig.java —— 手动注册所有 Bean,维护噩梦
@Configuration
public class AppConfig {

    @Bean
    public StudentController studentController() {
        return new StudentController(studentService());
    }

    @Bean
    public StudentService studentService() {
        return new StudentService(studentDao(), notificationManager());
    }

    @Bean
    public StudentDao studentDao() {
        return new StudentDao(jdbcTemplate());
    }

    @Bean
    public NotificationManager notificationManager() {
        return new NotificationManager(emailSender(), smsSender(), wechatSender());
    }

    @Bean
    public EmailSender emailSender() {
        return new EmailSender();
    }

    @Bean
    public SmsSender smsSender() {
        return new SmsSender();
    }

    @Bean
    public WechatSender wechatSender() {
        return new WechatSender();
    }

    @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

痛点:

  1. 每新增一个组件,都需要修改 AppConfig
  2. 构造器参数变化时,需要同步修改 @Bean 方法
  3. 配置类臃肿,可读性差
  4. 容易遗漏注册,导致 NoSuchBeanDefinitionException

操作后:使用 @ComponentScan 自动扫描

项目包结构:

com.feixiang.student
├── config
│   └── AppConfig.java          ← @Configuration
├── controller
│   └── StudentController.java   ← @Controller
├── service
│   ├── StudentService.java      ← @Service
│   └── NotificationManager.java ← @Service
├── dao
│   └── StudentDao.java          ← @Repository
└── component
    ├── EmailSender.java         ← @Component
    ├── SmsSender.java           ← @Component
    └── WechatSender.java        ← @Component
// 操作后:AppConfig.java —— 一行注解,自动扫描所有组件
@Configuration
@ComponentScan("com.feixiang.student")
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/student_db");
        config.setUsername("root");
        config.setPassword("secret");
        return new HikariDataSource(config);
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
// 操作后:各组件标注对应注解,无需手动注册
@Repository
public class StudentDao {
    private final JdbcTemplate jdbcTemplate;
    public StudentDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

@Service
public class StudentService {
    private final StudentDao studentDao;
    private final NotificationManager notificationManager;
    public StudentService(StudentDao studentDao, NotificationManager notificationManager) {
        this.studentDao = studentDao;
        this.notificationManager = notificationManager;
    }
}

@Component
public class EmailSender {
    public void send(String to, String subject, String content) {
        System.out.println("[邮件] 发送给:" + to);
    }
}

运行结果及分析

容器启动日志:
[main] INFO  o.s.c.a.AnnotationConfigApplicationContext - 
    Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6d06d69c
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.controller.StudentController
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.service.StudentService
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.service.NotificationManager
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.dao.StudentDao
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.component.EmailSender
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.component.SmsSender
[main] DEBUG o.s.c.c.ClassPathBeanDefinitionScanner - 
    Identified candidate component class: com.feixiang.student.component.WechatSender
[main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - 
    Pre-instantiating singletons in ...
    defining beans [appConfig, studentController, studentService, notificationManager, 
                     studentDao, emailSender, smsSender, wechatSender, dataSource, 
                     jdbcTemplate, transactionManager]

关键观察:

  1. 扫描器自动识别了 7 个组件类(controller、service、dao、component 包下的所有注解类)
  2. 加上 3 个显式 @Bean 定义(dataSource、jdbcTemplate、transactionManager),共 10 个 Bean
  3. 无需手动维护 Bean 注册列表

高级过滤规则

排除特定类或包

@Configuration
@ComponentScan(
    basePackages = "com.feixiang.student",
    excludeFilters = {
        // 排除 @Controller 类(如果 Web 层由 Spring MVC 单独扫描)
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
        // 排除特定类
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = TestDataLoader.class),
        // 排除符合正则的类名
        @ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*")
    }
)
public class AppConfig {
}

自定义包含规则

@Configuration
@ComponentScan(
    basePackages = "com.feixiang.student",
    useDefaultFilters = false,  // 关闭默认过滤器
    includeFilters = {
        // 只扫描标注了 @CustomService 的类
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = CustomService.class),
        // 扫描实现了特定接口的类
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MessageSender.class)
    }
)
public class AppConfig {
}

使用 basePackageClasses 避免字符串硬编码

@Configuration
@ComponentScan(basePackageClasses = StudentService.class)  // 扫描 StudentService 所在包及其子包
public class AppConfig {
}

basePackageClasses 的优势:如果包名重构,IDE 会自动更新类引用,而字符串 "com.feixiang.student" 需要手动修改。


易错场景与面试考点

反例一:扫描路径遗漏子包

小崔把 @ComponentScan 只配置到父包,但某些组件放在平行包下:

// ❌ 错误:扫描路径不包含所有组件
@Configuration
@ComponentScan("com.feixiang.student.service")  // 只扫描 service 包
public class AppConfig {
}
// 包路径:com.feixiang.student.dao.StudentDao
@Repository
public class StudentDao {
}

启动报错:

NoSuchBeanDefinitionException: No qualifying bean of type 'com.feixiang.student.dao.StudentDao' available

纠正:扫描父包,自动包含所有子包:

// ✅ 正确:扫描父包
@Configuration
@ComponentScan("com.feixiang.student")
public class AppConfig {
}

反例二:重复扫描导致 Bean 覆盖

小崔同时使用了 @ComponentScan 和 @Import,导致同一个类被注册两次:

// ❌ 错误:AppConfig 扫描了 config 包,同时 @Import 又导入了自己
@Configuration
@ComponentScan("com.feixiang.student")  // 扫描范围包含 config 包
@Import(AppConfig.class)  // 又导入了自己!
public class AppConfig {
}

纠正:避免扫描范围包含配置类自身,或避免循环 @Import:

// ✅ 正确:扫描业务包,不扫描 config 包
@Configuration
@ComponentScan(basePackages = "com.feixiang.student",
    excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AppConfig {
}

反例三:Spring Boot 中重复配置 @ComponentScan

在 Spring Boot 项目中,小崔既用了 @SpringBootApplication 又手动加了 @ComponentScan:

// ❌ 错误:@SpringBootApplication 已包含 @ComponentScan,手动配置会覆盖默认扫描路径
@SpringBootApplication
@ComponentScan("com.feixiang.student.service")  // 覆盖了默认的当前包扫描!
public class StudentApplication {
}

问题:@SpringBootApplication 默认扫描当前包及其子包。手动添加 @ComponentScan 会完全覆盖默认配置,导致当前包下的其他组件(如 controller、dao)不被扫描。

纠正:使用 @ComponentScan 的 basePackages 扩展扫描范围,而非覆盖:

// ✅ 正确:使用 @SpringBootApplication 的默认扫描,或显式包含所有需要的包
@SpringBootApplication
@ComponentScan({"com.feixiang.student", "com.feixiang.common"})
public class StudentApplication {
}

面试高频题

Q1:@ComponentScan 的扫描原理是什么?

Spring 使用 ASM 库直接读取类路径下 .class 文件的二进制内容,无需将类加载到 JVM。对于每个类,检查其注解及元注解链是否包含 @Component。如果通过过滤器检查,则注册为 BeanDefinition。扫描是递归的,会遍历指定包及其所有子包。

Q2:@ComponentScan 和 @ContextConfiguration 有什么区别?

@ComponentScan 用于配置类,开启组件自动扫描;@ContextConfiguration 用于测试类,指定测试时加载的配置文件或配置类。两者使用场景完全不同。

Q3:如何精确控制扫描范围?

① 使用 basePackages 指定具体包路径;② 使用 basePackageClasses 以类为锚点避免字符串硬编码;③ 使用 includeFilters 和 excludeFilters 自定义过滤规则;④ 设置 useDefaultFilters = false 完全自定义扫描逻辑。

Q4:为什么 @ComponentScan 扫描不到某些类?

常见原因:① 类不在扫描路径下;② 类没有标注 @Component 或其派生注解;③ 类被 excludeFilters 排除;④ 类是接口或抽象类(无法实例化);⑤ 类在 .jar 包中但扫描路径未包含。

上一页
@Component 详解
下一页
@Import 详解