找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2278

积分

0

好友

351

主题
发表于 昨天 21:52 | 查看: 3| 回复: 0

Spring BeanDefinition 深度解析:IoC容器背后的配置“图纸”

对于 DIY 爱好者和工厂制造来说,“图纸”的重要性不言而喻。在 Spring 框架的世界里,BeanDefinition 就扮演着这个核心“图纸”的角色,而最终被创建和管理的 Bean 对象,则是按照这份图纸生产出来的“产品”。理解 BeanDefinition,是深入理解 Spring IoC(控制反转)容器工作原理的关键一步。

本文将从传统的 XML 配置视角切入,剖析 BeanDefinition 如何作为“结构信息图纸”,指导 Spring 容器创建和管理对象。

一份“结构信息”图纸

这份“图纸”详细描述了 Bean 的静态属性和构成要素,主要包括以下几部分:

1. 类信息 (Class Information)

这是图纸最基础的部分,指明了要生产什么“产品”。

  • 核心内容:Bean 的全限定类名。容器通过反射技术实例化 Bean 完全依赖于这个信息。
  • XML 配置体现:通过 <bean> 标签的 class 属性指定。
<bean id="person" class="com.example.Person"/>

2. 作用域 (Scope)

定义了产品的“生产模式”,是单件生产还是按需定制。

  • 核心内容:规定 Bean 的生命周期范围,例如单例 (singleton)、原型 (prototype)、请求 (request)、会话 (session) 等。
  • XML 配置体现:通过 <bean> 标签的 scope 属性指定。
<bean id="userService" class="com.example.UserService" scope="prototype"/>

3. 依赖关系 (Dependencies)

定义了产品组装所需的“零部件”,即该 Bean 所依赖的其他 Bean 或值。

  • 构造器注入:使用 <constructor-arg> 子元素。
  • Setter 注入:使用 <property> 子元素。
  • 显式依赖声明:通过 <bean> 标签的 depends-on 属性指定,用于强制控制 Bean 的初始化顺序。

在 BeanDefinition 内部,这些依赖关系通过特定的方法暴露:

  • getConstructorArgumentValues(): 获取基于构造器的依赖注入参数。
  • getPropertyValues(): 获取基于 Setter 方法或字段的依赖注入参数。
  • getDependsOn(): 获取当前 Bean 初始化所强制依赖的其他 Bean 名称数组。

对应的 XML 配置示例如下:

构造器注入:

<bean id="userService" class="com.example.UserService">
    <constructor-arg ref="userDao"/> <!-- 引用其他Bean -->
    <constructor-arg value="100"/> <!-- 注入字面值 -->
</bean>

Setter 注入:

<bean id="userService" class="com.example.UserService">
    <property name="dao" ref="userDao"/>
    <property name="timeout" value="3000"/>
</bean>

显式依赖声明:

<bean id="beanA" class="com.example.BeanA" depends-on="beanB,beanC"/>

4. 继承关系

允许一份图纸基于另一份图纸进行设计,实现配置的复用和覆盖。

  • 核心内容:通过 getParentName() 可以获取父 BeanDefinition 的名称。子定义可以继承父定义的通用配置,并覆盖特定的部分。
  • XML 配置体现:通过 <bean> 标签的 parent 属性指定。
<!-- 抽象父定义,作为模板 -->
<bean id="abstractDataSource" class="...DataSource" abstract="true">
    <property name="maxPoolSize" value="10"/>
</bean>

<!-- 子定义,继承父定义的 maxPoolSize 配置,并覆盖 url -->
<bean id="myDataSource" parent="abstractDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
</bean>

从图纸到产品:Spring 的视角

看到这里,你可能会想:创建一个对象不就是 new Student() 吗?何必弄得这么复杂?的确,在简单场景下确实如此。但在现代企业级应用中,对象的创建、依赖管理、生命周期控制等“低级操作”早已交由 Spring 这样的框架来统一处理。

Spring 就像一个智能工厂,它读取我们提供的“图纸”(BeanDefinition),然后自动完成“产品”(Bean)的组装和生产。我们甚至不必亲自设计每份图纸,而是遵循 Spring 的规范(注解或配置格式)“照葫芦画瓢”即可。

下面,让我们站在 Spring 的角度,看看它如何根据 XML 配置信息(图纸),一步步“new”出对象:

场景一:最简单的 Bean

  • 图纸 (XML):
    <bean id="person" class="com.example.Person"/>
  • Spring 的执行逻辑:
    new Person();

场景二:构造器注入

  • 图纸 (XML):
    <bean id="userService" class="com.example.UserService">
        <constructor-arg ref="userDao"/> <!-- 引用其他Bean -->
        <constructor-arg value="100"/> <!-- 注入字面值 -->
    </bean>
  • Spring 的执行逻辑:
    // 假设 userDao 已由容器创建并管理
    UserDao userDao = ...;
    UserService userService = new UserService(userDao, 100);

场景三:Setter 注入

  • 图纸 (XML):
    <bean id="userService" class="com.example.UserService">
        <property name="dao" ref="userDao"/>
        <property name="timeout" value="3000"/>
    </bean>
  • Spring 的执行逻辑:
    UserService userService = new UserService();
    userService.setDao(userDao);
    userService.setTimeout(3000);

场景四:继承配置

  • 图纸 (XML):
    <bean id="abstractDataSource" class="...DataSource" abstract="true">
        <property name="maxPoolSize" value="10"/>
    </bean>
    <bean id="myDataSource" parent="abstractDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
    </bean>
  • Spring 的执行逻辑:
    // 1. 创建子Bean的实例(父Bean是抽象的,不实例化)
    DataSource myDataSource = new DataSource();
    // 2. 应用从父Bean继承的配置:设置 maxPoolSize 为 10
    myDataSource.setMaxPoolSize(10);
    // 3. 应用子Bean自身的配置:设置 url
    myDataSource.setUrl("jdbc:mysql://localhost:3306/mydb");

图纸的来源与处理流程

那么,Spring 是如何获得这些“图纸”的呢?它支持多种配置来源,并拥有一套完整的解析、筛选和注册流程。这套流程可以概括为以下几个核心步骤:

  1. 配置来源:Spring 支持多种方式定义 Bean。

    • 注解配置:使用 @Component@Service@Repository@Controller 等系列注解。
    • XML 配置文件:使用传统的 <bean> 标签进行定义。
    • Java 配置类:使用 @Configuration 注解标注的类,结合 @Bean 注解进行定义。
  2. 解析与读取:针对不同的配置来源,Spring 使用不同的“阅读器”进行解析。

    • 对于注解配置,由 ClassPathBeanDefinitionScanner 负责扫描类路径。
    • 对于 XML 配置,由 XmlBeanDefinitionReaderBeanDefinitionParserDelegate 协作解析。
    • 对于 Java 配置类,由 ConfigurationClassBeanDefinitionReader 负责处理。
    • 在底层,Spring 会使用 MetadataReader 基于 ASM 字节码技术读取类的元数据信息。
  3. 筛选与检查:并非所有被扫描或读取的类都会成为 Bean。Spring 会进行一系列筛选和条件检查。

    • 使用 excludeFilters(排除过滤器)和 includeFilters(包含过滤器)进行初步筛选。
    • 执行 @Conditional 条件注解检查,只有满足特定条件(如环境变量、类是否存在等)的类才会继续。
    • 进行类检查,确保目标是具体类(非接口、非抽象类)。
  4. 生成与注册:通过所有检查后,Spring 会根据来源生成相应类型的 BeanDefinition 对象。

    • ScannedGenericBeanDefinition: 由扫描注解类生成。
    • GenericBeanDefinition: 由解析 XML 配置生成。
    • AnnotatedGenericBeanDefinition: 由解析 @Configuration 配置类生成。
    • 最终,通过 registerBeanDefinition() 方法,将 BeanDefinition 注册到 Spring 容器的核心存储中——一个名为 BeanDefinitionMapConcurrentHashMap<String, BeanDefinition>,该映射由 BeanDefinitionRegistry 注册表接口管理。在需要时(例如处理继承关系),Spring 还会对 BeanDefinition 进行合并处理,形成最终用于实例化的完整定义。

这套精密的机制,确保了 Spring 能够灵活、高效地从各种配置中“读懂”我们的意图,并据此构建出完整的应用对象图。理解 BeanDefinition 这份“图纸”,是掌握 Spring 容器灵魂的第一步,也为后续理解 Bean 的生命周期、AOP、以及更高级的 后端与架构 特性奠定了坚实基础。

参考资料

[1] 我们来学spring -- BeanDefinition是个啥, 微信公众号:https://mp.weixin.qq.com/s/kP1Ul66_a1gcRrmkFeGsmw

版权声明:本文由 云栈社区 整理发布,版权归原作者所有。




上一篇:智能防丢器设计鉴赏:解析蓝牙BLE与GPS定位技术的硬件创新
下一篇:Spring IoC内核解析:基于DefaultListableBeanFactory构建最小依赖容器
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-10 01:00 , Processed in 0.327843 second(s), 49 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表