Spring 依赖注入是如何实现的?

Spring 依赖注入(Dependency Injection,DI)是 Spring 框架的核心特性之一,它负责管理对象之间的依赖关系,降低了代码的耦合度,提高了代码的可测试性和可维护性。Spring 实现依赖注入的方式主要有以下几种,并结合底层原理进行说明:

1. 构造器注入(Constructor Injection):

  • 原理: Spring 通过调用 Bean 的构造方法来注入依赖。
  • 实现方式:
    • XML 配置: 使用 <constructor-arg> 标签。
    • 注解: 使用 @Autowired 注解(可以省略,Spring 4.3+ 支持在构造方法上隐式注入)。
    • JavaConfig:@Bean 方法中使用参数。
  • 优点:
    • 依赖关系在对象创建时就确定,保证了对象在使用时所有依赖都已就绪。
    • 依赖不可变(immutable),提高了对象的安全性。
    • 更容易发现依赖问题(编译时就能发现)。
  • 缺点:
    • 当依赖较多时,构造方法参数列表会很长,不够灵活。
    • 不适用于有多个可选依赖的情况。

示例:

public class MyService {
    private MyRepository myRepository;

    // 构造器注入
    @Autowired // 可省略
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

2. Setter 方法注入(Setter Injection):

  • 原理: Spring 通过调用 Bean 的 setter 方法来注入依赖。
  • 实现方式:
    • XML 配置: 使用 <property> 标签。
    • 注解: 在 setter 方法上使用 @Autowired 注解。
    • JavaConfig:@Bean 方法中调用 setter 方法。
  • 优点:
    • 更灵活,可以在对象创建后修改依赖。
    • 适用于可选依赖。
  • 缺点:
    • 依赖关系在对象创建后才建立,可能导致在使用时依赖尚未注入(需要进行 null 检查)。
    • 依赖可变,可能导致对象状态不一致。

示例:

public class MyService {
    private MyRepository myRepository;

    // Setter 方法注入
    @Autowired
    public void setMyRepository(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}

3. 字段注入(Field Injection):

  • 原理: Spring 通过反射直接设置 Bean 的字段值来注入依赖。
  • 实现方式:
    • 注解: 在字段上使用 @Autowired 注解。
  • 优点:
    • 最简洁,代码量最少。
  • 缺点:
    • 依赖关系不明确,可读性较差。
    • 破坏了封装性,可以直接修改私有字段。
    • 难以进行单元测试(需要使用反射来模拟依赖)。
    • 容易产生循环依赖

示例:

public class MyService {
    // 字段注入
    @Autowired
    private MyRepository myRepository;
}

4. 方法注入(Method Injection,不常用):

  • 通过Lookup方法或者CGLIB代理,在每次调用方法时,获取新的实例。

底层原理(简化版):

  1. BeanDefinition 解析: Spring 容器启动时,会解析配置文件(XML、注解、JavaConfig),将 Bean 的定义信息(类名、依赖关系、作用域等)封装成 BeanDefinition 对象。
  2. Bean 实例化: 当需要获取 Bean 时,Spring 根据 BeanDefinition 中的信息,通过反射创建 Bean 的实例。
  3. 依赖注入:
    • 构造器注入: Spring 根据 BeanDefinition 中定义的构造方法参数,从容器中查找或创建相应的依赖对象,然后调用构造方法创建 Bean 实例。
    • Setter 方法注入: Spring 根据 BeanDefinition 中定义的属性和 setter 方法,从容器中查找或创建相应的依赖对象,然后调用 setter 方法注入依赖。
    • 字段注入: Spring 根据 BeanDefinition 中定义的字段和 @Autowired 注解,从容器中查找或创建相应的依赖对象,然后通过反射直接设置字段值。
  4. Bean 初始化: 依赖注入完成后,Spring 会调用 Bean 的初始化方法(如果有)。
  5. Bean 放入容器: 初始化完成后,Spring 将 Bean 放入容器中,供应用程序使用。

循环依赖问题:

  • 什么是循环依赖: 当两个或多个 Bean 相互依赖时,就会形成循环依赖。例如,A 依赖 B,B 依赖 A。
  • Spring 如何解决:
    • 构造器注入的循环依赖无法解决,会抛出异常。

    • Setter 方法注入和字段注入的循环依赖,Spring 可以通过“三级缓存”机制解决。

      • 一级缓存(singletonObjects): 存放完全初始化好的单例 Bean。
      • 二级缓存(earlySingletonObjects): 存放早期暴露的 Bean 实例(已创建但未完全初始化)。
      • 三级缓存(singletonFactories): 存放创建 Bean 实例的 ObjectFactory
    • 解决过程(简化版):

      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 通过多种方式实现依赖注入,包括构造器注入、Setter 方法注入和字段注入。底层原理是基于反射和 BeanDefinition 对象。Spring 通过“三级缓存”机制解决了 Setter 方法注入和字段注入的循环依赖问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰糖心书房

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

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

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

打赏作者

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

抵扣说明:

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

余额充值