Spring 系列(二):加载 BeanDefinition 的几种方式

Spring 系列(二):BeanDefinition 是如何被加载进容器的

Spring 容器启动的第一件大事,并不是创建 Bean,而是构建容器结构

而容器结构的核心载体,正是 BeanDefinition

这一篇作为 Spring 系列的第一篇,我们只做一件事:

系统梳理 Spring 中 BeanDefinition 的所有加载方式,以及它们各自解决什么问题。


一、先统一一个认知:Spring 到底在"加载"什么?

在讨论"加载 Bean 的方式"之前,必须先纠正一个常见但模糊的说法。

Spring 启动早期,并不会加载 Bean 实例,而是加载:

text 复制代码
BeanDefinition

它描述的是:

  • 这个 Bean 是什么类型
  • 该如何创建
  • 作用域是什么
  • 依赖关系如何

所有加载方式的终点,都是向容器中注册 BeanDefinition。

只要你抓住这一点,后面所有方式都只是"入口不同,本质一致"。


二、最原始的方式:基于 XML 的 BeanDefinition 加载

这是 Spring 最早期、也是最直观的一种方式。

xml 复制代码
<bean id="userService" class="com.example.UserService" />

1. 本质做了什么?

  • 解析 XML
  • 为每个 <bean> 标签构建一个 BeanDefinition
  • 注册到 BeanDefinitionRegistry

2. 对应的核心组件

  • XmlBeanDefinitionReader
  • DefaultListableBeanFactory

3. 这种方式的意义

  • 奠定了 "配置 → BeanDefinition → 容器" 的模型
  • 也是后续所有加载方式的设计基础

三、基于配置类的方式:@Configuration + @Bean

随着 Java Config 的出现,Spring 提供了更类型安全的配置方式。

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    public UserService userService() {
        return new UserService();
    }
}

1. BeanDefinition 是什么时候生成的?

并不是在方法执行时,而是在解析配置类阶段

  • @Configuration 会被识别为配置类
  • @Bean 方法会被解析为 BeanDefinition
  • 实例化逻辑被包装为 FactoryMethod

2. 核心处理器

  • ConfigurationClassPostProcessor

它本质上是一个 BeanDefinitionRegistryPostProcessor


四、基于类路径扫描:@Component 体系

这是目前使用最广泛的一种方式。

java 复制代码
@Component
@Service
@Repository
@Controller

1. 工作流程

text 复制代码
扫描 classpath
  ↓
读取 class 元信息(不加载类)
  ↓
匹配注解条件
  ↓
构建 BeanDefinition
  ↓
注册到容器

2. 关键点

  • 扫描阶段不会触发类加载
  • 只依赖 ASM 读取字节码元信息

3. 核心组件

  • ClassPathBeanDefinitionScanner

五、通过 @Import 扩展 BeanDefinition 来源

@Import 是 Spring 中极其重要、但经常被低估的一个入口。

java 复制代码
@Import({A.class, B.class})
public class AppConfig {}

1. @Import 的三种形态

1️⃣ 直接导入普通类
  • 被当作普通 Bean 注册
2️⃣ ImportSelector
java 复制代码
public class MySelector implements ImportSelector {
    public String[] selectImports(...) {
        return new String[]{"com.example.A"};
    }
}
  • 根据条件动态决定注册哪些 BeanDefinition
3️⃣ ImportBeanDefinitionRegistrar
  • 直接操作 BeanDefinitionRegistry
  • 精确、底层、强控制力

六、通过 FactoryBean 间接定义 Bean

java 复制代码
public class MyFactoryBean implements FactoryBean<UserService> {
    public UserService getObject() {
        return new UserService();
    }
}

1. 特点

  • 容器中注册的是 FactoryBean 的 BeanDefinition
  • 实际暴露的是 getObject() 返回的对象

2. 使用场景

  • 框架级对象构建
  • 复杂初始化逻辑

七、容器级扩展点:BeanDefinitionRegistryPostProcessor

这是最高优先级、最底层的扩展方式。

1. 能做什么?

  • 在所有 Bean 实例化之前
  • 动态注册、修改、删除 BeanDefinition

2. 典型使用场景

  • Spring Boot 自动装配
  • Dubbo / MyBatis 等框架接入

八、一张总览图(思维模型)

text 复制代码
XML / @Bean / @Component / @Import
            ↓
   BeanDefinitionReader / Scanner
            ↓
   BeanDefinitionRegistry
            ↓
      冻结容器结构

入口可以多样,但终点只有一个。


九、为什么 Spring 要设计这么多加载方式?

不是为了复杂,而是为了:

  • 不同配置风格
  • 不同扩展粒度
  • 不同控制级别

但无论哪一种,Spring 都严格遵守一条原则:

一切能力,先落到 BeanDefinition 上。


十、总结

Spring 的启动,并不是"创建 Bean",而是"构建容器结构"。

而 BeanDefinition 的加载方式,决定了:

  • Bean 从哪里来
  • 是否存在
  • 以什么形态存在

在下一篇中,我们将从 BeanDefinition 冻结之后 继续,正式进入:

Bean 是如何被创建出来,并被生命周期精细管理的。

这,才是 IoC 真正开始生效的地方。

相关推荐
颜酱2 小时前
前端算法必备:双指针从入门到很熟练(快慢指针+相向指针+滑动窗口)
前端·后端·算法
小当家.1052 小时前
Maven与Gradle完整对比指南:构建工具深度解析
java·gradle·maven
p***s912 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
AI架构师之家2 小时前
一文分清机器学习、深度学习和各类 AI 工具的关系:心法与招式的区别
后端·ai编程
neoooo2 小时前
🍃Spring Boot 多模块项目中 Parent / BOM / Starter 的正确分工
java·后端·架构
黄金贼贼2 小时前
2026最新java单元测试json校验器
java·单元测试·json
菜鸟的迷茫2 小时前
为了防雪崩加了限流,结果入口先挂了
java·后端·架构
荒诞硬汉2 小时前
数组常见算法
java·数据结构·算法
悟空码字2 小时前
SpringBoot整合MongoDB,性能提升,优化实践
java·spring boot·后端