原文链接:https://blog.csdn.net/qq_41907991/article/details/107164508
1、注入方式
注入方式 | 优点 | 缺点 |
字段注入 | 简单 | 添加依赖无限制,类能无限膨胀,违法单一职责 隐藏了依赖关系,依赖关系不明显 无法保证字段的不变性 依赖注入容器紧耦合,依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖对象。换句话说,这个类应该是一个简单的POJO能够被单独实例化并且你也能为它提供它所需的依赖。只有这样,你才能在单元测试中实例化这个类而不必去启动依赖注入容器,实现测试分离 |
setter注入 | 依赖明确 | 仅使用setter注入的依赖对象需要进行非空检查;对象无法在构造完成后马上进入就绪状态 |
构造器注入 | 依赖明确 对象在构造完成之后,即已进入就绪状态,可以马上使用 | 可以注入final字段确保字段不变性 当依赖对象比较多的时候,构造方法的参数列表会比较长,维护和使用也比较麻烦,根据单一职责原则,此时应该考虑重构了。使用不慎还有可能出现循环依赖。 |
2、spring中解决循环依赖的条件
- 类必须是单例
- 依赖注入的方式不能全是构造器注入的方式
以A、B两个类为例
(Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建)
依赖情况 | 依赖注入方式 | 是否解决循环依赖 |
AB相互依赖 | 字段注入 | 是 |
AB相互依赖 | setter方法注入 | 是 |
AB相互依赖 | 构造器注入 | 否 |
AB相互依赖 | A中注入B的方式为setter注入, B中注入A的方式为构造器 | 是 |
AB相互依赖 | A中注入B的方式为构造器注入, B中注入A的方式为setter | 否 |
3、循环依赖原理--三级缓存
代码入口DefaultSingletonBeanRegistry
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
- 一级缓存,singletonObjects,存储所有已创建完毕的单例 Bean (完整的 Bean)。
- 二级缓存,earlySingletonObjects,存储所有仅完成实例化,但还未进行属性注入和初始化的 Bean。
- 三级缓存,singletonFactories,存储能建立这个 Bean 的一个工厂,通过工厂能获取这个 Bean,延迟化 Bean 的生成,工厂生成的 Bean 会塞入二级缓存
这三个 map 是如何配合的呢?
- 首先,获取单例 Bean 的时候会通过 BeanName 先去 singletonObjects(一级缓存) 查找完整的 Bean,如果找到则直接返回,否则进行步骤 2。
- 看对应的 Bean 是否在创建中,如果不在直接返回null,说明这个bean还未创建,标记这个bean为创建中,调用createBean创建bean,如果是,则会去 earlySingletonObjects (二级缓存)查找 Bean,如果找到则返回,否则进行步骤 3
- 去 singletonFactories (三级缓存)通过 BeanName 查找到对应的工厂,如果存在工厂则通过工厂创建 Bean ,并且放置到 earlySingletonObjects 中。
- 如果三个缓存都没找到,则返回 null。
流程示例:
获取A--> 一级缓存中没找到--> 不在创建中--> 进行创建A-->A实例化
-->A注入属性时
-->发现依赖B-->获取B-->创建B-->B实例化
-->B注入属性时
-->发现依赖A
-->A正在创建中
-->一级缓存没找到A
-->二级缓存也没找到
-->三级缓存中找到,工厂获取getObject,加入到二级缓存,从三级缓存移除
-->B属性注入成功-->B调用初始化完成
-->B加入到一级缓存
-->A属性注入完成-->A初始化完成-->A加入到一级缓存,从二级缓存移除
4、三级缓存缓存的是什么
三级缓存为什么要使用工厂而不是直接使用引用?
这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,并且这个对象需要代理才去提前生成代理对象,否则返回的是对象本身。即使没有循环依赖,也会将其添加到三级缓存中,但是不会去通过这个工厂去真正创建对象,因为Spring是不知道会不会有循环依赖发生。
5、三级缓存是否是必须的
假设不使用三级缓存,直接在二级缓存中的时候判断这个 Bean 是否需要代理,如果要直接放代理的对象
这样分析看下来是没有任何毛病的,三级缓存的加入也没有起到提高效率的作用,
spring这样设计的原因是和bean的生命周期有关
正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的,所以如果你提早代理了其实是违背了 Bean 定义的生命周期
6、不能解决的循环依赖
- 多实例bean通过setter注入时
- 发生循环依赖时,不能全是构造器注入的方式(如上分析,和类加载顺序有关)
- 单例的代理bean通过setter注入时
- 设置了@dependsOn注解的bean
7、解决方式
- 不使用构造器注入,换成set注入或者字段注入
- 使用@Lazy,将注入的bean设置为延迟加载,注入的是一个代理非完全初始化的对象,当它首次被使用时才完全初始化。
- 按上文分析,实例化之后再调用初始化方法,可以将依赖关系在初始化方法中设置, 比如:
class A implements ApplicationContextAware, InitializingBean { private B b; private ApplicationContext context; public B getB() { return b; } @Override public void afterPropertiesSet() throws Exception { b = context.getBean(B.class); } @Override public void setApplicationContext(final ApplicationContext ctx) throws BeansException { context = ctx; } }
总结:
面试官:”Spring是如何解决的循环依赖?“
答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
面试官:”为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?“
答:如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。