← 返回题目列表

什么是 BeanDefinition?Spring 是怎么从「配置」变成「Bean」的?

困难 第 27 / 30 题 更新于 2026/07/28
BeanDefinitionBeanDefinitionRegistrySpring容器元数据

简化版

BeanDefinition 是 Spring 里「一个 Bean 的配置元数据」的抽象——它不是 Bean 实例本身,而是「描述这个 Bean 该怎么创建」的一张「说明书」:类是什么、是单例还是多例、依赖哪些 Bean、初始化方法是哪个、是否懒加载、构造参数是什么……Spring 创建 Bean 的过程本质是「两阶段」① 加载解析阶段——把各种配置来源(XML、@Component 注解、@Bean 方法、@Configuration 类)统统解析成 BeanDefinition 对象,注册到 BeanDefinitionRegistry(通常就是 DefaultListableBeanFactory)里,此时还没有任何 Bean 实例,只有一堆「说明书」;② 实例化阶段——容器根据每个 BeanDefinition「说明书」去反射创建实例、注入依赖、初始化,得到真正的 Bean。所以 BeanDefinition 是「配置」和「实例」之间的中间表示,它把「五花八门的配置方式」统一成「一种内部数据结构」,让后续创建逻辑不用关心配置到底来自 XML 还是注解。

详细版

BeanDefinition 里有什么(核心属性):

属性含义
beanClassNameBean 的类全限定名
scope作用域:singleton / prototype
lazyInit是否懒加载
dependsOn依赖哪些 Bean(先创建它们)
propertyValues属性注入的值/引用
constructorArgumentValues构造器参数
initMethodName / destroyMethodName初始化/销毁方法
factoryBeanName / factoryMethodName由哪个工厂方法创建(@Bean 方法)
primary / autowireMode是否首选、自动装配模式

从配置到 Bean 的两阶段

阶段一:配置 → BeanDefinition(元数据),注册到 registry
  XML <bean>          ─┐
  @Component 扫描      ─┼→ 解析成 BeanDefinition ─→ BeanDefinitionRegistry
  @Bean 方法          ─┤                            (DefaultListableBeanFactory)
  @Import 等          ─┘   此时只有"说明书",没有实例

阶段二:BeanDefinition → Bean 实例
  遍历 BeanDefinition → 反射实例化 → 依赖注入 → 初始化 → 单例池
// 编程式注册一个 BeanDefinition(框架内部就是这么干的)
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(UserService.class);
bd.setScope("singleton");
bd.setLazyInit(false);
// 注册到 registry(BeanFactory 通常实现了 BeanDefinitionRegistry)
registry.registerBeanDefinition("userService", bd);

⚠️ 理解 BeanDefinition 是理解 Spring 容器的钥匙——很多「高级玩法」都是在「BeanDefinition 阶段」动手脚:BeanDefinitionRegistryPostProcessor(如 MyBatis 的 MapperScannerConfigurer、Spring Boot 的自动配置)能在实例化之前动态增删改 BeanDefinitionBeanFactoryPostProcessor(如 PropertySourcesPlaceholderConfigurer)能修改 BeanDefinition 的属性(如把 ${} 占位符替换成真实值)。因为这些都发生在「有说明书、没实例」的阶段,所以能「凭空造 Bean」或「改 Bean 的定义」而不影响已创建的实例——这正是 Spring 可扩展性的根基。

完整版教学

一、BeanDefinition 是什么:Bean 的「说明书」

理解 BeanDefinition 的第一步,是分清「Bean 的定义」和「Bean 的实例」:

Bean 实例:真正 new 出来的对象,能调它的方法(如 userService.save())
BeanDefinition:描述"这个 Bean 该怎么造"的元数据,本身不是对象

打个比方:
  BeanDefinition = 建筑图纸(写着几层楼、什么材料、怎么建)
  Bean 实例      = 按图纸建好的房子
  → 先有图纸(定义),才能建房子(实例)
  → 一张图纸可以建多栋房子(prototype:一个定义造多个实例)

BeanDefinition 是「Bean 的配置元数据的抽象」——它记录「这个 Bean 是什么类、单例还是多例、依赖谁、怎么初始化」等信息,但它本身不是 Bean 实例。这个「定义与实例分离」的设计很关键:容器先收集所有「图纸」(BeanDefinition),再按图纸「盖房子」(创建实例)。理解「BeanDefinition 是 Bean 的说明书/图纸、不是实例本身、定义与实例分离」,就抓住了理解 Spring 容器的钥匙。

二、为什么需要这层抽象:统一各种配置方式

BeanDefinition 存在的核心价值是「统一五花八门的配置方式」:

Spring 配置 Bean 的方式有很多种:
  ① XML:<bean id="x" class="..."/>
  ② 注解扫描:@Component / @Service / @Repository
  ③ Java 配置:@Bean 方法
  ④ @Import、编程式注册、Spring Boot 自动配置...

问题:如果后续"创建 Bean"的逻辑要分别处理这 N 种配置,会很乱

解决:先把所有配置方式,统一解析成同一种数据结构 —— BeanDefinition
  XML 解析器      → BeanDefinition
  注解扫描器      → BeanDefinition
  @Bean 方法解析  → BeanDefinition
  → 之后创建 Bean 的逻辑只认 BeanDefinition,不关心配置来自哪

这是典型的「中间表示(IR)」设计思想——就像编译器把各种高级语言先编译成统一的中间码,再由后端生成机器码。BeanDefinition 是 Spring 的「中间表示」:把 XML、注解、Java 配置等统一成一种数据结构,让「解析配置」和「创建 Bean」两个环节解耦。加一种新配置方式,只需写一个「配置 → BeanDefinition」的解析器,创建逻辑完全不用改。理解「BeanDefinition 是统一各种配置的中间表示、解耦解析和创建、加新配置方式只需加解析器」,就理解了它的设计价值。

三、BeanDefinition 里有什么

具体看 BeanDefinition 记录了哪些「创建 Bean 需要的信息」:

BeanDefinition 的核心信息(回答"怎么造这个 Bean"):
  - beanClassName:造哪个类的实例(反射要用)
  - scope:singleton(造一个共享)还是 prototype(每次造新的)
  - lazyInit:容器启动就造,还是用到才造
  - constructorArgumentValues:构造器要传什么参数
  - propertyValues:要注入哪些属性(值或对另一个 Bean 的引用)
  - initMethodName / destroyMethodName:造好后调哪个初始化方法、销毁时调哪个
  - factoryBeanName / factoryMethodName:如果是 @Bean 方法造的,记住工厂和方法
  - dependsOn:造这个之前,得先造好哪些 Bean
  - primary:同类型多个候选时,是不是首选

这些属性覆盖了「创建一个 Bean 的完整信息」——造什么(beanClass)、造几个(scope)、什么时候造(lazyInit)、怎么造(构造参数/工厂方法)、造好后怎么装配(属性注入)、初始化和销毁(回调方法)、造的顺序(dependsOn)。容器后续就是「读这些属性,一步步把 Bean 造出来」。理解「BeanDefinition 记录 beanClass/scope/lazyInit/构造参数/属性/init-destroy/dependsOn 等创建 Bean 的完整信息」,就理解了它承载的内容。

四、BeanDefinitionRegistry:存放说明书的地方

解析出来的 BeanDefinition 存在哪?答案是 BeanDefinitionRegistry

BeanDefinitionRegistry = 存放所有 BeanDefinition 的"注册表"
  核心方法:
    registerBeanDefinition(name, bd)  注册一个定义
    getBeanDefinition(name)           取出一个定义
    removeBeanDefinition(name)        删除一个定义
    containsBeanDefinition(name)      是否存在

谁实现了它?
  DefaultListableBeanFactory —— Spring 最核心的 BeanFactory 实现
  它既是 BeanFactory(能创建/获取 Bean)
  又是 BeanDefinitionRegistry(能注册/管理 BeanDefinition)
  → 一身二职:既存"说明书",又负责按说明书"造 Bean"

BeanDefinitionRegistry 是「BeanDefinition 的注册表」——增删改查 BeanDefinition。最核心的实现是 DefaultListableBeanFactory,它「一身二职」:既是 BeanFactory(能创建/获取 Bean 实例),又是 BeanDefinitionRegistry(能注册/管理 BeanDefinition)。所以它内部有一个 Map<String, BeanDefinition> 存所有定义,创建 Bean 时从这个 Map 里取定义。理解「BeanDefinitionRegistry 是 BeanDefinition 的注册表、DefaultListableBeanFactory 既是 BeanFactory 又是 Registry、内部 Map 存所有定义」,就理解了 BeanDefinition 的存放和管理。

五、两阶段:从配置到实例的完整链路

把「配置 → BeanDefinition → Bean」的完整链路串起来:

阶段一:加载解析(配置 → BeanDefinition,只有说明书没实例)
  1. 容器启动,读取配置(XML/注解扫描/@Configuration)
  2. 各种解析器把配置解析成 BeanDefinition
  3. registerBeanDefinition 注册到 registry 的 Map
  4. ★ BeanFactoryPostProcessor 介入:此时可以改 BeanDefinition
     - PlaceholderConfigurer 把 ${} 占位符替换成真实值
     - BeanDefinitionRegistryPostProcessor 可增删 BeanDefinition
       (MyBatis 的 Mapper 扫描、Spring Boot 自动配置都在这)

阶段二:实例化(BeanDefinition → Bean 实例)
  5. 遍历所有非懒加载的单例 BeanDefinition
  6. 对每个:反射实例化 → 依赖注入 → BeanPostProcessor 前置
     → 初始化方法 → BeanPostProcessor 后置(AOP 代理在这)
  7. 放入单例池,容器就绪

关键是两阶段之间有个「窗口期」——阶段一结束、阶段二开始前,所有 BeanDefinition 都已注册但还没实例化,这时 BeanFactoryPostProcessor 可以修改定义(如替换占位符、增删 Bean)。这就是为什么 Spring 能「动态注册 Bean」而不冲突。理解「阶段一解析注册 BeanDefinition(BFPP 可改定义)、阶段二按定义实例化(BPP 可包装实例含 AOP)、两阶段之间是扩展窗口」,就理解了 Spring 从配置到 Bean 的完整链路。

六、动态操作 BeanDefinition:框架的扩展根基

BeanDefinition 阶段的可操作性,是很多框架「魔法」的根基:

① BeanDefinitionRegistryPostProcessor:动态增删 BeanDefinition
   - MyBatis 的 MapperScannerConfigurer:扫描 @Mapper 接口,
     为每个接口"凭空"注册一个 BeanDefinition(用 FactoryBean 造代理)
   - Spring Boot 自动配置:根据条件注册一堆 BeanDefinition
   → 因为是"注册定义",所以能凭空造出 Bean

② BeanFactoryPostProcessor:修改已注册的 BeanDefinition
   - PropertySourcesPlaceholderConfigurer:
     把 propertyValues 里的 ${jdbc.url} 替换成配置文件的真实值
   → 在实例化前改定义,不影响任何已创建的实例

为什么能这么玩?
  因为这些操作都在"有说明书、没实例"的阶段
  改说明书 → 后面按新说明书造 Bean,天经地义

BeanDefinition 阶段的「可编程性」是 Spring 扩展性的核心——BeanDefinitionRegistryPostProcessor 能动态注册 Bean(MyBatis 为每个 Mapper 接口凭空注册代理 Bean、Spring Boot 自动配置按条件注册),BeanFactoryPostProcessor 能修改定义(替换 ${} 占位符)。这些之所以可行,是因为都发生在「有定义、没实例」的窗口期——改定义不影响实例。理解「BeanDefinitionRegistryPostProcessor 动态注册 Bean(MyBatis/Spring Boot)、BeanFactoryPostProcessor 改定义(占位符),是 Spring 扩展性的根基」,就理解了 BeanDefinition 的终极价值——它让容器「可编程地生产 Bean」。

记忆钩子:「BeanDefinition = 一个 Bean 的配置元数据(说明书/图纸),不是实例本身;记录 beanClass/scope/lazyInit/构造参数/属性/init-destroy/dependsOn;它是统一各种配置(XML/注解/@Bean)的中间表示,解耦’解析配置’和’创建 Bean’;存在 BeanDefinitionRegistry(即 DefaultListableBeanFactory 的 Map);两阶段:①解析配置→BeanDefinition注册(BFPP 可改定义、BDRPP 可增删 Bean,MyBatis/SpringBoot 在此)②按定义反射实例化→注入→初始化(BPP 可包装含 AOP);改定义在’有说明书没实例’的窗口期,是 Spring 扩展性根基」

七、常见误区与追问

  • 误区:BeanDefinition 就是 Bean 实例。 不是——BeanDefinition 是「描述 Bean 怎么创建」的元数据(说明书/图纸),本身不是对象;容器先收集所有 BeanDefinition,再按它们创建实例;一个 prototype 的 BeanDefinition 可造多个实例。
  • 误区:Spring 一启动就把所有 Bean 都创建好了。 分两阶段——先把所有配置解析成 BeanDefinition 注册(此时无实例),再实例化;且懒加载(lazyInit)的单例和 prototype 不在启动时创建,用到才造。
  • 误区:不同配置方式(XML/注解/@Bean)的 Bean 创建逻辑不同。 恰恰相反——它们都先被解析成统一的 BeanDefinition,之后创建逻辑只认 BeanDefinition、不关心配置来源;这正是 BeanDefinition 作为「中间表示」的价值。
  • 误区:Bean 一旦定义就不能改了。 在实例化之前,BeanFactoryPostProcessor 可以修改 BeanDefinition(改属性、替换占位符),BeanDefinitionRegistryPostProcessor 可以增删 BeanDefinition——这是 MyBatis 扫描 Mapper、Spring Boot 自动配置的原理。
  • 追问:MyBatis 的 Mapper 接口没有实现类,Spring 是怎么把它变成 Bean 的? MapperScannerConfigurer(一个 BeanDefinitionRegistryPostProcessor)扫描 @Mapper 接口,为每个接口注册一个 BeanDefinition,其 beanClass 设为 MapperFactoryBean(一个 FactoryBean),由它用 JDK 动态代理生成接口的代理实例——凭空「造」出了 Bean。
  • 追问:${} 占位符是什么时候被替换成真实值的? 在 BeanFactoryPostProcessor 阶段——PropertySourcesPlaceholderConfigurer 遍历所有 BeanDefinition 的 propertyValues,把 ${jdbc.url} 这类占位符替换成 Environment/配置文件里的真实值;发生在实例化之前,所以注入时拿到的已是真实值。
  • 追问:BeanFactoryPostProcessor 和 BeanPostProcessor 有什么区别? BeanFactoryPostProcessor 作用于「BeanDefinition」(实例化之前,改定义);BeanPostProcessor 作用于「Bean 实例」(实例化之后初始化前后,包装实例,AOP 代理就在这);一个管「说明书」,一个管「造好的房子」。

八、加强记忆

BeanDefinition 是「一个 Bean 的配置元数据」的抽象——是「描述这个 Bean 该怎么创建」的说明书/图纸(记录 beanClassscopelazyInit、构造参数、属性注入、init/destroy 方法、dependsOnprimary 等),本身不是 Bean 实例(定义与实例分离,一个 prototype 定义可造多个实例)。它的核心价值是作为「中间表示」——把 XML、@Component 注解、@Bean 方法等五花八门的配置统一成一种数据结构,解耦「解析配置」和「创建 Bean」(加新配置方式只需加解析器)。所有 BeanDefinition 存在 BeanDefinitionRegistry(即 DefaultListableBeanFactory 内部的 Map,它一身二职既是 BeanFactory 又是 Registry)。Spring 从配置到 Bean 是两阶段① 解析注册(配置→BeanDefinition,此时只有说明书没实例,BeanFactoryPostProcessor 可改定义如替换 ${} 占位符、BeanDefinitionRegistryPostProcessor 可增删 Bean 如 MyBatis 扫 Mapper、Spring Boot 自动配置);② 实例化(按定义反射创建→依赖注入→初始化,BeanPostProcessor 可包装实例,AOP 代理在此)。两阶段之间的「有定义没实例」窗口期是 Spring 扩展性的根基。一句话「BeanDefinition 是 Bean 的说明书(不是实例),统一各种配置为中间表示存在 Registry;两阶段:解析成 BeanDefinition(BFPP/BDRPP 可改可增删,MyBatis/SpringBoot 在此)→ 按定义实例化(BPP 包装含 AOP)」。