Spring 如何解析 Bean 之间的依赖关系,并自动注入依赖?

Spring 解析 Bean 之间的依赖关系并自动注入依赖,是 Spring IoC 容器的核心功能。这个过程可以分为几个关键步骤,涉及 BeanDefinition 的解析、依赖关系的识别、依赖对象的查找和创建、以及依赖的注入:

1. BeanDefinition 的解析和注册:

  • 扫描和解析: Spring 容器启动时,会扫描指定的包(通过 <context:component-scan>@ComponentScan 或 JavaConfig 中的 @Configuration 类)或读取 XML 配置文件。
  • BeanDefinition 创建: 对于每个符合条件的类(例如带有 @Component@Service@Repository@Controller 等注解的类,或 XML 中配置的 <bean> 元素),Spring 会创建一个 BeanDefinition 对象。
    • BeanDefinition 包含了 Bean 的所有元数据信息,例如:
      • Bean 的类名 (Class Name)
      • Bean 的作用域 (Scope,如 singleton、prototype)
      • Bean 的构造方法参数 (Constructor Arguments)
      • Bean 的属性 (Properties)
      • Bean 的初始化方法 (Init Method)
      • Bean 的销毁方法 (Destroy Method)
      • Bean 的依赖关系 (Dependencies)
      • Bean 是否延迟初始化 (Lazy Init)
      • …等等
  • BeanDefinition 注册: Spring 将创建好的 BeanDefinition 对象注册到 BeanDefinitionRegistry 中(通常是 DefaultListableBeanFactory)。BeanDefinitionRegistry 是一个 Map 结构,以 Bean 的名称作为 key,BeanDefinition 作为 value。

2. 依赖关系的识别:

Spring 通过以下几种方式识别 Bean 之间的依赖关系:

  • 构造器注入:
    • XML 配置: Spring 解析 <constructor-arg> 标签,获取依赖 Bean 的名称或类型。
    • 注解: Spring 检测构造方法上的 @Autowired 注解(或隐式构造方法注入),根据构造方法参数的类型来确定依赖。
    • JavaConfig: Spring 解析 @Bean 方法的参数,根据参数类型确定依赖。
  • Setter 方法注入:
    • XML 配置: Spring 解析 <property> 标签,获取依赖 Bean 的名称或类型。
    • 注解: Spring 检测 setter 方法上的 @Autowired 注解,根据 setter 方法参数的类型来确定依赖。
    • JavaConfig: Spring 在创建 Bean 实例后,会调用相应的 setter 方法来注入依赖。
  • 字段注入:
    • 注解: Spring 检测字段上的 @Autowired 注解,根据字段的类型来确定依赖。
  • @Resource:
    • 注解: 优先按照名字注入,如果没有找到,再按照类型。
  • 接口注入
    • 如果实现类只有一个,那么直接注入。
    • 如果一个接口有多个实现类, 可以通过 @Qualifier 或者 @Primary 等注解来指定具体注入哪一个。

3. 依赖对象的查找和创建:

当 Spring 需要为一个 Bean 注入依赖时,它会执行以下步骤:

  • 按名称查找: 如果依赖是通过名称指定的(例如 <property name="xxx" ref="yyy"/>@Qualifier("yyy")),Spring 会直接从 BeanDefinitionRegistry 或已创建的 Bean 实例中查找名为 “yyy” 的 Bean。
  • 按类型查找: 如果依赖是通过类型指定的(例如构造方法参数、setter 方法参数、字段类型),Spring 会从 BeanDefinitionRegistry 或已创建的 Bean 实例中查找与该类型匹配的 Bean。
    • 多个匹配项: 如果找到多个匹配类型的 Bean,Spring 会根据以下规则进行处理:
      • @Primary 注解: 如果其中一个 Bean 标记了 @Primary 注解,Spring 会选择该 Bean。
      • @Qualifier 注解: 如果注入点使用了 @Qualifier 注解指定了 Bean 的名称,Spring 会选择该 Bean。
      • 类型匹配: 如果没有 @Primary@Qualifier 注解,Spring 会尝试根据字段名、setter方法名、构造器参数名 与 Bean 名称 进行匹配。
      • 抛出异常: 如果以上规则都无法确定唯一的 Bean,Spring 会抛出 NoUniqueBeanDefinitionException 异常。
  • 创建依赖对象: 如果容器中没有找到所需的依赖对象,Spring 会根据该依赖对象的 BeanDefinition 创建一个新的实例(递归地进行依赖注入)。

4. 依赖的注入:

找到依赖对象后,Spring 会通过以下方式将依赖注入到目标 Bean 中:

  • 构造器注入: 通过调用目标 Bean 的构造方法,将依赖对象作为参数传入。
  • Setter 方法注入: 通过调用目标 Bean 的 setter 方法,将依赖对象作为参数传入。
  • 字段注入: 通过反射,直接将依赖对象设置到目标 Bean 的字段中。

5. 循环依赖的处理(简化版):

Spring 通过“三级缓存”机制解决 Setter 方法注入和字段注入的循环依赖(构造器注入的循环依赖无法解决):

  • 一级缓存(singletonObjects): 存放完全初始化好的单例 Bean。
  • 二级缓存(earlySingletonObjects): 存放早期暴露的 Bean 实例(已创建但未完全初始化,属性可能还未注入)。
  • 三级缓存(singletonFactories): 存放创建 Bean 实例的 ObjectFactory

当 A 依赖 B,B 依赖 A 时:

  1. 创建 A 实例,将 A 的 ObjectFactory 放入三级缓存,并将一个早期的 A 实例(未完成属性注入)放入二级缓存。
  2. A 注入 B,发现 B 不存在,开始创建 B。
  3. 创建 B 实例,将 B 的 ObjectFactory 放入三级缓存,并将一个早期的 B 实例放入二级缓存。
  4. B 注入 A,从二级缓存中获取早期的 A 实例(虽然 A 还未完全初始化,但 B 可以先持有 A 的引用)。
  5. B 完成初始化,放入一级缓存。
  6. A 继续初始化,注入 B(从一级缓存中获取),完成初始化,放入一级缓存。

总结:

Spring 依赖注入的实现是一个复杂而精细的过程,它涉及 BeanDefinition 的解析、依赖关系的识别、依赖对象的查找和创建、依赖的注入、以及循环依赖的处理等多个步骤。Spring 通过反射、注解、XML 解析等技术,实现了自动化、灵活的依赖注入,大大简化了 Spring 应用程序的开发。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰糖心书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值