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代理,在每次调用方法时,获取新的实例。
底层原理(简化版):
[*]BeanDefinition 解析: Spring 容器启动时,会解析配置文件(XML、注解、JavaConfig),将 Bean 的定义信息(类名、依赖关系、作用域等)封装成 BeanDefinition 对象。
[*]Bean 实例化: 当需要获取 Bean 时,Spring 根据 BeanDefinition 中的信息,通过反射创建 Bean 的实例。
[*]依赖注入:
[*]构造器注入: Spring 根据 BeanDefinition 中定义的构造方法参数,从容器中查找或创建相应的依赖对象,然后调用构造方法创建 Bean 实例。
[*]Setter 方法注入: Spring 根据 BeanDefinition 中定义的属性和 setter 方法,从容器中查找或创建相应的依赖对象,然后调用 setter 方法注入依赖。
[*]字段注入: Spring 根据 BeanDefinition 中定义的字段和 @Autowired 注解,从容器中查找或创建相应的依赖对象,然后通过反射直接设置字段值。
[*]Bean 初始化: 依赖注入完成后,Spring 会调用 Bean 的初始化方法(假如有)。
[*]Bean 放入容器: 初始化完成后,Spring 将 Bean 放入容器中,供应用程序使用。
循环依赖问题:
[*]什么是循环依赖: 当两个或多个 Bean 相互依赖时,就会形成循环依赖。例如,A 依赖 B,B 依赖 A。
[*]Spring 如何解决:
[*] 构造器注入的循环依赖无法解决,会抛出非常。
[*] Setter 方法注入和字段注入的循环依赖,Spring 可以通过“三级缓存”机制解决。
[*]一级缓存(singletonObjects): 存放完全初始化好的单例 Bean。
[*]二级缓存(earlySingletonObjects): 存放早期暴露的 Bean 实例(已创建但未完全初始化)。
[*]三级缓存(singletonFactories): 存放创建 Bean 实例的 ObjectFactory。
[*] 解决过程(简化版):
[*]创建 A 实例,将 A 的 ObjectFactory 放入三级缓存,并暴露一个早期的 A 实例到二级缓存。
[*]A 注入 B,发现 B 不存在,开始创建 B 实例。
[*]创建 B 实例,将 B 的 ObjectFactory 放入三级缓存,并暴露一个早期的 B 实例到二级缓存。
[*]B 注入 A,从二级缓存中获取早期的 A 实例(虽然 A 还未完全初始化,但 B 可以先持有 A 的引用)。
[*]B 完成初始化,放入一级缓存。
[*]A 继续初始化,注入 B(从一级缓存中获取),完成初始化,放入一级缓存。
总结:
Spring 通过多种方式实现依赖注入,包括构造器注入、Setter 方法注入和字段注入。底层原理是基于反射和 BeanDefinition 对象。Spring 通过“三级缓存”机制解决了 Setter 方法注入和字段注入的循环依赖问题。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]