Spring创建BeanDefinition之路径扫描详解

 更新时间:2025年04月07日 09:50:26   作者:程序员侠客行  
这篇文章主要介绍了Spring创建BeanDefinition之路径扫描方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

一、从示例开始

当我们创建AnnotationConfigApplicationContext对象时,Spring底层到底做了些什么?

来看下面示例。

package com.xiakexing;

import com.xiakexing.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
		UserService userService = context.getBean("userService", UserService.class);
		userService.test();
    }
}
package com.xiakexing;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(value = "com.xiakexing")
public class AppConfig {

}
package com.xiakexing.service;

import org.springframework.stereotype.Component;

@Component
public class UserService {

    public void test() {
        System.out.println("hello spring");
    }
}

我们猜测这几行代码执行逻辑:

  • new AnnotationConfigApplicationContext(AppConfig.class)时,从AppConfig类解析扫描路径即@ComponentScan;
  • 遍历扫描路径下的所有Java类,如果某个类上有@Component、@Service等注解,Spring就为这个类创建BeanDefinition,保存到Map中,比如Map<String, Class>,key是根据规则生成的beanName,value就是当前类的class对象。
  • context.getBean("userService")时,Spring根据beanName找到类的class对象,反射调用构造器创建对象。

带着上面的猜想,我们来看看源码。本文暂且关注路径扫描的实现,随后的文章将讲解BeanDefinition的创建过程。

二、创建AnnotationConfigApplicationContext

构造方法的入参是componentClasses,即可以传入多个配置类。

this()中创建了AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner,将用于扫描指定路径下的类,创建BeanDefinition。

JFR 是 Java Flight Record (Java飞行记录),是JVM 内置的基于事件的JDK监控记录框架。StartupStep是Spring基于JFR对运行过程的监控,阅读源码时可忽略它。

注意,AnnotationConfigApplicationContext间接实现了BeanDefinitionRegistry接口,具备向容器中注册BeanDefinition的能力。

在创建ClassPathBeanDefinitionScanner对象时,指定了使用DefaultFilters:将扫描所有带有@Component注解的类。

总结:this()仅仅实例化了容器对象,创建了Reader、Scanner,用于解析类信息。

三、注册Configuration类

3.1 创建BeanDefinition

register(componentClasses),显然是将配置类注册到容器中。

来看AnnotatedBeanDefinitionReader#doRegisterBean的核心逻辑:

  • 为配置类创建AnnotatedGenericBeanDefinition对象;
  • 处理@Conditional,如果条件不满足,将舍弃这个类;
  • 给BeanDefinition对象属性赋值;
  • 生成beanName,解析@Lazy、@Primary、@DependsOn等注解;
  • 创建BeanDefinitionHolder对象,发起注册。

3.2 注册BeanDefinition

BeanDefinitionHolder类只是对BeanDefinition的包装,仅有三个属性:beanDefinition、beanName和aliases。

在BeanDefinitionReaderUtils#registerBeanDefinition中

最终会调用DefaultListableBeanFactory#registerBeanDefinition方法,执行这几行代码:

看到了BeanFactory的两个核心数据结构:

  • beanDefinitionMap保存了beanName与beanDefinition的映射;
  • beanDefinitionNames保存了所有的beanName

果然与我们当初的猜想一致。

至此,配置类已被添加到beanDefinitionMap中,可是@ComponentScan指定的包路径,在哪儿被处理了呢?

四、扫描包路径

先说结论:@ComponentScan包路径下的类,是在ClassPathBeanDefinitionScanner#scan中被处理的。

来看AnnotationConfigApplicationContext的另一个构造方法:入参就是指定包路径。

转调到ClassPathBeanDefinitionScanner#scan。

基于配置类创建AnnotationConfigApplicationContext时,是在哪儿调了scan()或doScan()呢?答案就在refresh()中。

4.1 BeanFactoryPostProcessor接口

先看类注释:

Factory hook that allows for custom modification of an application context's bean definitions, adapting the bean property values of the context's underlying bean factory.

工厂钩子,允许自定义修改应用程序上下文的bean定义,调整上下文的底层bean工厂的bean属性值。

A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.

BeanFactoryPostProcessor可以与bean定义交互和修改,但不能与bean实例交互。

可见,该接口是BeanFactory的后置处理器,在创建Bean实例前,干涉BeanDefinition创建和更新。

仅有一个方法:

void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

该接口有个重要的子接口BeanDefinitionRegistryPostProcessor,能够向容器注册更多的BeanDefinition。

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in.

对标准BeanFactoryPostProcessor SPI的扩展,允许在常规BeanFactoryPostProcessor检测开始之前注册进一步的bean定义。

4.2 ConfigurationClassPostProcessor类

源码中,BeanDefinitionRegistryPostProcessor接口仅有唯一实现ConfigurationClassPostProcessor。

在ConfigurationClassPostProcessor#processConfigBeanDefinitions中,

检查已注册的每一个BeanDefinition,是否是候选配置类(或组件),满足以下任意条件即可:

  • 类上有@Configuration注解;
  • 类上有以下任意一个注解;

  • 类中有@Bean注解的方法;

得到Set<BeanDefinitionHolder> candidates后,会调用ConfigurationClassParser.parse()

接下来会遍历处理每一个候选类

在ConfigurationClassParser#doProcessConfigurationClass中,解析@ComponentScan、@ComponentScans注解;

  • 执行Filter逻辑后,得到basePackages路径集;
  • 调用ClassPathBeanDefinitionScanner#doScan,为路径下的Bean创建BeanDefinition,并注册到容器中。

关于doScan方法的详细逻辑,我们下一篇再看。

五、逻辑闭环

要用ConfigurationClassPostProcessor来处理配置类,Spring容器中就得先有该类的实例。那么,这个类是何时注册到容器中的?

答案就在new AnnotatedBeanDefinitionReader(this)中:

为ConfigurationClassPostProcessor创建BeanDefinition并注册。

当执行AbstractApplicationContext#refresh时,其中有一步是调用容器中BeanFactoryPostProcessor接口所有实现。

此时,ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry将被执行。

流程图

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • SpringBoot配置HTTPS的详细步骤

    SpringBoot配置HTTPS的详细步骤

    本文介绍了配置SpringBoot项目HTTPS的全流程,包括开发环境生成自签名证书和生产环境配置正式SSL证书,以及配置HTTP自动跳转HTTPS以提升用户体验,注意事项和常见问题也一并进行了说明,需要的朋友可以参考下
    2026-03-03
  • SpringBoot解析自定义yml文件的流程步骤

    SpringBoot解析自定义yml文件的流程步骤

    这篇文章主要介绍了SpringBoot解析自定义yml文件的流程步骤,文章通过代码示例和图文结合的方式给大家介绍的非常详细, 对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-06-06
  • SpringBoot打包前重新拉取maven依赖的方法实现

    SpringBoot打包前重新拉取maven依赖的方法实现

    在使用 Maven 构建 Spring Boot 项目时,如果希望在每次打包时都强制拉取依赖,可以通过以下方法实现,本文给大家介绍了四种实现方法,并通过代码讲解的非常详细,需要的朋友可以参考下
    2024-12-12
  • org.apache.ibatis.annotations不存在的问题

    org.apache.ibatis.annotations不存在的问题

    这篇文章主要介绍了org.apache.ibatis.annotations不存在的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • SSM项目实现短信验证码登录功能的示例代码

    SSM项目实现短信验证码登录功能的示例代码

    这篇文章主要为大家分享了在SSM项目中实现短信验证码登录功能的示例代码,文中的实现步骤讲解详细,感兴趣的小伙伴可以跟随小编一起动手尝试一下
    2022-05-05
  • java实现投票程序设计

    java实现投票程序设计

    这篇文章主要介绍了java实现投票程序设计,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • 项目总结之HttpURLConnection的disconnect的问题

    项目总结之HttpURLConnection的disconnect的问题

    这篇文章主要介绍了项目总结之HttpURLConnection的disconnect的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • SpringBoot创建并简单使用的实现

    SpringBoot创建并简单使用的实现

    这篇文章主要介绍了SpringBoot创建并简单使用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • JetCache 缓存框架的使用及源码解析(推荐)

    JetCache 缓存框架的使用及源码解析(推荐)

    JetCache是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。本文重点给大家介绍JetCache 缓存框架的使用及源码分析,感兴趣的朋友一起看看吧
    2022-01-01
  • 详解Java的设计模式编程中的原型模式

    详解Java的设计模式编程中的原型模式

    这篇文章主要介绍了Java的设计模式编程中的原型模式,处理对象复制时要特别注意浅拷贝和深拷贝的问题,需要的朋友可以参考下
    2016-02-02

最新评论