Spring doScan源码详解

doScan 方法是 Spring 框架中用于扫描指定包路径并注册 Bean 定义的核心方法,通常在组件扫描(如 @ComponentScan)时触发。以下是逐行代码解析及其调用时机说明:

/**
 * 扫描指定的基础包路径,查找所有候选组件(如标记了 @Component 的类),
 * 并将它们注册为 Spring 容器中的 BeanDefinition。
 * 此方法由 ClassPathBeanDefinitionScanner 的 scan() 方法调用,
 * 通常在 Spring 容器启动时处理 @ComponentScan 或 XML 的 <context:component-scan> 时触发。
 */
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 校验基础包路径不能为空
    Assert.notEmpty(basePackages, "At least one base package must be specified");
    
    // 存储最终注册的 BeanDefinitionHolder(包含 BeanDefinition 和 BeanName)
    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();

    // 遍历所有基础包路径
    for (String basePackage : basePackages) {
        // 1. 扫描包路径下的类文件,解析带有 @Component 的类,生成候选 BeanDefinition
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

        // 遍历所有候选 BeanDefinition
        for (BeanDefinition candidate : candidates) {
            // 2. 解析 Bean 的作用域(如 @Scope("prototype"),默认 singleton)
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());

            // 3. 生成 Bean 的名称(默认使用类名首字母小写,或通过 @Bean 指定)
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

            // 4. 后置处理 AbstractBeanDefinition(设置默认值,如懒加载、初始化方法等)
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }

            // 5. 处理通用注解(如 @Lazy、@Primary、@DependsOn)
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }

            // 6. 检查是否允许注册该 Bean(避免重复注册)
            if (checkCandidate(beanName, candidate)) {
                // 包装 BeanDefinition 和 BeanName
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                
                // 7. 处理作用域代理模式(如 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS))
                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(
                    scopeMetadata, definitionHolder, this.registry
                );

                // 将 BeanDefinitionHolder 加入结果集,以便后续使用
                beanDefinitions.add(definitionHolder);
                
                // 8. 注册 BeanDefinition 到容器(最终存入 DefaultListableBeanFactory 的 beanDefinitionMap)
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

1. 方法定义

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {

作用:扫描指定的包路径,查找候选 Bean 并注册到容器。
参数:basePackages 表示要扫描的基础包路径(如 com.example.service)。
返回值:包含所有注册的 Bean 定义及其名称的集合(BeanDefinitionHolder)。

2. 参数校验

Assert.notEmpty(basePackages, "At least one base package must be specified");

作用:检查 basePackages 是否为空,若为空则抛出异常。
意义:确保至少指定一个包路径,避免无效扫描。

3. 初始化结果集合

Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();

作用:使用 LinkedHashSet 存储 Bean 定义,保证插入顺序。

4. 遍历所有基础包

for (String basePackage : basePackages) {

作用:逐个处理每个包路径,确保所有指定包都被扫描。

5. 查找候选 Bean 定义

Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

作用:调用 findCandidateComponents 扫描包路径下的类文件,识别带有注解(如 @Component)的类,生成 BeanDefinition。

底层实现:通过类路径扫描(ClassPathScanning)实现,筛选候选 Bean。

6. 处理每个候选 Bean

for (BeanDefinition candidate : candidates) {

作用:逐个处理扫描到的候选 Bean。

7. 解析作用域元数据

ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());

作用:解析 Bean 的作用域(如 singleton、prototype),默认通过 @Scope 注解配置。

示例:若类上有 @Scope(“prototype”),则设置作用域为 prototype。

8. 生成 Bean 名称

String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);

作用:生成 Bean 的唯一名称,默认规则是类名首字母小写(如 userService)。

扩展:可自定义 BeanNameGenerator 实现个性化命名。

9. 后处理 Bean 定义

if (candidate instanceof AbstractBeanDefinition) {
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}

作用:对 AbstractBeanDefinition 进行后处理,设置默认属性(如懒加载、初始化方法)。

典型操作:若未显式配置 init-method,可能在此处设置默认值。

10. 处理通用注解

if (candidate instanceof AnnotatedBeanDefinition) {
    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}

作用:解析类上的通用注解(如 @Lazy、@Primary、@DependsOn),将其配置应用到 Bean 定义。

示例:@Lazy 会设置 Bean 的懒加载属性为 true。

11. 检查候选 Bean 是否可注册

if (checkCandidate(beanName, candidate)) {

作用:检查容器中是否已存在同名 Bean,根据策略决定是否覆盖或忽略。

策略:若已存在同名 Bean 且不允许覆盖,则跳过注册。

12. 创建 Bean 定义持有者

BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);

作用:将 Bean 定义与名称封装为 BeanDefinitionHolder,方便后续处理。

13. 应用作用域代理模式

definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);

作用:根据作用域(如 request、session)创建代理对象,确保作用域行为正确。

示例:对于 request 作用域的 Bean,生成代理以保证每次 HTTP 请求返回新实例。

14. 注册 Bean 定义

beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);

作用:将 BeanDefinitionHolder 添加到结果集合并注册到容器(如 DefaultListableBeanFactory)。

注册逻辑:调用 BeanDefinitionRegistry.registerBeanDefinition() 完成注册。

15. 返回结果集合

return beanDefinitions;

作用:返回所有成功注册的 Bean 定义。

doScan 方法调用时机

  1. 组件扫描触发
    场景:当使用 @ComponentScan 注解或在 XML 中配置 context:component-scan 时。
    流程:Spring 容器启动时,ComponentScanBeanDefinitionParser 解析配置,最终调用 ClassPathBeanDefinitionScanner.scan(),进而触发 doScan。
  2. 自动配置阶段
    场景:Spring Boot 的 @SpringBootApplication 隐含 @ComponentScan,启动时自动扫描主类所在包及其子包。
    流程:在 refreshContext() 阶段处理 BeanDefinitionRegistryPostProcessor,触发扫描。
  3. 自定义扫描逻辑
    场景:通过编程方式调用扫描(如 new ClassPathBeanDefinitionScanner(registry).scan(“com.example”))。

示例:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.scan("com.example");
context.refresh();

总结

代码作用:doScan 是 Spring 组件扫描的核心方法,负责扫描包路径、解析 Bean 元数据、生成名称、处理注解,并最终注册 Bean 定义。
调用时机:在 Spring 容器初始化阶段,由组件扫描配置触发(如 @ComponentScan),或在编程式扫描时显式调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值