《Spring Boot 3:高级与架构设计》第 1 章 Bean 与 BeanDefinition 个人理解 2

《Spring Boot 3:高级与架构设计》第 1 章 Bean 与 BeanDefinition 个人理解 2

1 Bean 和 BeanDefinition 的关系是什么

BeanDefinition 是 Bean 的元信息,BeanDefinition 中包含了 Bean 的定义信息,

类似于类是对象的元信息,类中包含了对象的定义信息。

2 如何获取 Bean 对应的 BeanDefinition

2.1 新建一个演示工程

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.yangjunbo</groupId>
        <artifactId>springboot-example-jetli-b</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>spring-ioc-a</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.9</version>
        </dependency>
    </dependencies>

</project>

2.2 基于 XML 的 BeanDefinition

SpringFramework 的给 IOC 容器注入 Bean 的方式之一是基于 XML 配置文件。

使用 XML 配置文件的方式中,每定义一个标签就相当于构建了一个 BeanDefinition。

下面演示基于 XML 的 BeanDefinition。

以最简单的 Person 为例,使用 XML 配置文件的方式完成 Person 类型 Bean 的注册及属性注入,如代码所示。

java 复制代码
package com.yangjunbo.spring.definition.examplea;

/**
 * ClassName: Person
 * Package: com.yangjunbo.spring.definition.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 8:42
 * @Version 1.0
 */
public class Person {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }

}
java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.yangjunbo.spring.definition.examplea.Person">
        <property name="name" value="yangjunbo"/>
    </bean>

</beans>

接下来使用 XML 配置文件驱动 IOC 容器,也就是利用 ClassPathXmlApplicationContext 加载 XML 配置文件。

并尝试获取 BeanDefinition,但是借助 IDE 尝试后,并没有从 ClassPathXmlApplicationContext 中找到类似于 getBeanDefinition 的方法,只找到了一个获取名称和获取数量的方法,如图所示。

对于 IOC 容器而言,BeanDefinition 是一个相对底层的概念,所以使用 ClassPathXmlApplicationContext 无法正常获取,需要先从其中获取 BeanFactory,再借助 BeanFactory 才可以获取 BeanDefinition。

既然 ClassPathXmlApplicationContext 没有 getBeanDefinition 方法,那么就由它获取 BeanFactory,再使用 BeanFactory 获取BeanDefinition,如代码所示。编码完毕后运行启动类,观察控制台中打印 person 的 BeanDefinition 信息,可以发现 id 为 "person" 的BeanDefinition 中存储了这个 Bean 的所有基本信息。

java 复制代码
package com.yangjunbo.spring.definition.examplea;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: ExampleAApplication
 * Package: com.yangjunbo.spring.definition.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 8:51
 * @Version 1.0
 */
public class ExampleAApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:examplea/spring-examplea.xml");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

}

2.3 基于 @Component 的 BeanDefinition

SpringFramework 的给 IOC 容器注入 Bean 的方式之一是使用 @Component 加组件扫描。

给 Person 标注一个 @Component,并使用 AnnotationConfigApplicationContext 的组件扫描驱动IOC 容器,如代码所示。

与 ClassPathXmlApplicationContext 不同的是,在 AnnotationConfigApplicationContext 中可以直接调用getBeanDefinition 方法,这是与 XML 配置文件驱动的 IOC 容器不同的一点。

java 复制代码
package com.yangjunbo.spring.definition.examplea;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: ExampleAApplication
 * Package: com.yangjunbo.spring.definition.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 8:51
 * @Version 1.0
 */
public class ExampleAApplication {

    public static void main1(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:examplea/spring-examplea.xml");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.yangjunbo.spring.definition.examplea");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

}

运行启动类的 main 方法。

可以发现,BeanDefinition 的打印信息里最大的不同是加载来源:基于 XML 解析出来的 Bean 定义来源是 XML 配置文件;基于 @Component解析出来的 Bean 定义来源是类的 .class 文件。

2.4 基于 @Bean 的 BeanDefinition

SpringFramework 的给 IOC 容器注入 Bean 的方式之一是使用 @Bean 加自动配置类。

可以编写一个 BeanDefinitionQuickstartConfiguration 配置类,使用 @Bean 注册一个 Person,随后使用该配置类驱动 IOC 容器,并直接获取 BeanDefinition,如代码所示。

java 复制代码
package com.yangjunbo.spring.definition.examplea;

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

/**
 * ClassName: BeandifinitionConfiguration
 * Package: com.yangjunbo.spring.definition.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 12:44
 * @Version 1.0
 */
@Configuration
public class BeandifinitionConfiguration {

    @Bean
    public Person person() {
        Person person = new Person();
        person.setName("yangjunbo");
        return person;
    }

}
java 复制代码
package com.yangjunbo.spring.definition.examplea;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: ExampleAApplication
 * Package: com.yangjunbo.spring.definition.examplea
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 8:51
 * @Version 1.0
 */
public class ExampleAApplication {

    public static void main1(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:examplea/spring-examplea.xml");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

    public static void main2(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.yangjunbo.spring.definition.examplea");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeandifinitionConfiguration.class);
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("person");
        System.out.println(beanDefinition);
    }

}

运行启动类的 main 方法,发现控制台打印的内容与前面的内容相比有很大的区别,具体包含以下 4 点。

(1)Bean 的类型是 Root Bean(ConfigurationClassBeanDefinition 继承自 RootBeanDefinition)​。

(2)Bean 的 className 消失不见。

(3)自动注入模式为 AUTOWIRE_CONSTRUCTOR(根据 Bean 的构造方法注入)​。

(4)factoryBean 属性不为空:person 由 BeanDefinitionConfiguration 的 person 方法创建。

2.5 为什么不同方式注册的 Bean 在生成 BeanDefinition 时会有较大差异

BeanDefinition 的生成规则

(1)对于通过 XML 加载的 BeanDefinition,其读取工具是 XmlBeanDefinitionReader。

(2)对于通过模式注解 + 组件扫描的方式构造的 BeanDefinition,其扫描工具是 ClassPathBeanDefinitionScanner。

(3)通过配置类 + @Bean 的方式构造的 BeanDefinition,其扫描工具是 ConfigurationClassBeanDefinitionReader。

3 什么是 BeanDefinition 的合并

3.1 理解合并

从 BeanDefinition 的结构中获悉,IOC 容器中的 Bean 也存在父子关系。与 Class 的抽象、继承一样,标签中有一个 abstract 属性和 parent 属性,由这两个属性的相互配合就可以形成具有父子关系的 BeanDefinition。下面通过一个示例来演示 BeanDefinition 的合并效果。

3.2 合并示例

虚拟一个演示场景:所有的动物都归人饲养,而动物分很多种。

代码提供了一个 Person 类、一个抽象类 Animal 和一个实现类 Cat,其中 Cat 继承自 Animal。

java 复制代码
package com.yangjunbo.spring.definition.exampleb.bean;

public class Person {
    
}
java 复制代码
package com.yangjunbo.spring.definition.exampleb.bean;

public abstract class Animal {

    private Person person;
    
    public Person getPerson() {
        return person;
    }
    
    public void setPerson(Person person) {
        this.person = person;
    }

}
java 复制代码
package com.yangjunbo.spring.definition.exampleb.bean;

public class Cat extends Animal {
    
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "Cat{" + "name=" + name + ", person='" + getPerson() + '\'' + "}";
    }

}

要想体现出 BeanDefinition 的合并效果,需要使用 XML 配置文件来注册 Bean。

注册 Person 的方式与之前完全一致,但是注册 Animal 时需要注意,由于 Animal 是一个抽象类,而抽象类在 XML 配置文件中无法直接声明,因此若强行编写,IDEA 则会给出报错提示​,程序也无法正确运行。

正确的方式是在声明 Animal 的标签上标注 abstract="true",即代表这个类不参与注册 IOC 容器中具体类型的 Bean 注册,而是作为一个抽象定义表示。

有了抽象的 Animal 后,再注册 Cat 时就可以直接引用,标签上有一个 parent 属性,可以指定当前 Bean 继承哪个 Bean 中的信息,所以这里注册 Cat 时就需要标注 parent="animal"。

当声明 parent 后,cat 中的 person 属性就不再需要显式声明,Animal 中已经声明了 Person,IOC 容器会对这两个 Bean 的定义信息进行合并。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="com.yangjunbo.spring.definition.exampleb.bean.Person">
    </bean>

    <bean id="animal" class="com.yangjunbo.spring.definition.exampleb.bean.Animal" abstract="true">
        <property name="person" ref="person"/>
    </bean>

    <bean id="cat" class="com.yangjunbo.spring.definition.exampleb.bean.Cat" parent="animal">
        <property name="name" value="cat"/>
    </bean>

</beans>

最后使用 XML 配置文件驱动 IOC 容器,随后取出 cat 的 BeanDefinition 并打印。

从打印的结果中可以发现,虽然 cat 中的 person 被成功注入,但是打印的 BeanDefinition 是一个 Generic bean,而且还可以看到它的 parent 信息("animal")​。这个结果其实并不是 BeanDefinition 合并后的结果,也就是说,并没有获取到正确且完整的 BeanDefinition。

java 复制代码
package com.yangjunbo.spring.definition.exampleb;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: ExampleBApplication
 * Package: com.yangjunbo.spring.definition.exampleb
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 17:00
 * @Version 1.0
 */
public class ExampleBApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:exampleb/spring-exampleb.xml");
        BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("cat");
        System.out.println(beanDefinition);
    }

}

以 Debug 的形式重新运行 main 方法,在获取到的 BeanDefinition 中可以看到,内部的属性赋值信息中只包含 name,没有 person 属性,如图所示,这也证明了刚才所说的获取合并信息错误。

要想获得合并后的完整 BeanDefinition,需要用到 BeanFactory 的另一个方法:getMergedBeanDefinition。

这个方法的名称带有 "合并" 的意思,它来自 ConfigurableBeanFactory,用于合并本身的 BeanDefinition 信息与继承的父 BeanDefinition 信息。

换用 getMergedBeanDefinition 方法后重新运行 main 方法,发现此时控制台打印的 BeanDefinition 类型变为 Root bean,且没有出现与 parent 相关的信息;

java 复制代码
package com.yangjunbo.spring.definition.exampleb;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: ExampleBApplication
 * Package: com.yangjunbo.spring.definition.exampleb
 * Description:
 *
 * @Author 杨钧博
 * @Create 2026/9/7 17:00
 * @Version 1.0
 */
public class ExampleBApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:exampleb/spring-exampleb.xml");
        //BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition("cat");
        //System.out.println(beanDefinition);
        BeanDefinition mergedBeanDefinition = applicationContext.getBeanFactory().getMergedBeanDefinition("cat");
        System.out.println(mergedBeanDefinition);
    }

}

如果此时再以 Debug 的形式运行 main 方法,就可以发现此时的 propertyValues 中出现了两个属性键值对,如图所示,证明合并成功。

4 设计 BeanDefinition 的意义是什么

BeanDefinition 的基本使用已经演示完毕。

笔者希望能借助 BeanDefinition 传达一个设计理念:定义信息 → 实例。

该设计理念对于后续更深入地理解 IOC 容器和工作原理有至关重要的作用。

如同 Java 先编写类,再使用 new 关键字创建对象,

Spring Framework 在面对应用程序时也需要对其中的 Bean 进行定义抽取。

只有抽取成可以统一类型,才能在后续 Bean 的管理中进行统一管理,抑或是对特定的 Bean 进行特殊化处理。

这一统一类型就是 BeanDefinition 这个抽象类型。

相关推荐
qq_269506751 小时前
第30课-请求与响应
java
代码不停1 小时前
Spring Boot日志
spring boot·spring
日拱一卒的小田1 小时前
ZYNQ学习笔记4-ZYNQ的SD卡控制器2
笔记·学习
qq_269506751 小时前
第28课-AJAX与前端框架
java
IT_陈寒1 小时前
Redis主从切换竟让业务卡了3秒?这个坑我替你踩了
前端·人工智能·后端
程序员清风1 小时前
Java 后端如何接入大语言模型
java·spring boot·架构·aigc
江湖人称菠萝包1 小时前
【Qt】《Qt 5.9 C++开发指南》笔记-Chapter8-绘图
笔记·qt·qt5
xieliyu.1 小时前
JVM 垃圾回收机制详解:从标记过程、回收算法到垃圾收集器
java·jvm·笔记·java-ee
JavacKaka1 小时前
Redis大Key导致生产事故复盘博客-2026-09-18
java