Spring可以通过以下方法来避免循环依赖:
构造函数注入:使用构造函数注入来注入依赖项,这是一种比较安全的方式,因为在对象创建时就会注入依赖项,可以避免循环依赖。
Setter方法注入:使用Setter方法注入依赖项,Spring会在对象创建后调用Setter方法来注入依赖项,这种方式也可以避免循环依赖。
使用@Lazy注解:使用@Lazy注解来延迟依赖项的注入,这样可以避免循环依赖。
使用@DependsOn注解:使用@DependsOn注解来指定bean创建的顺序,可以避免循环依赖。
使用 @Lookup 注解: 在这种情况下,Spring 容器会在每次调用 @Lookup 注解修饰的方法时,返回一个新的 bean 实例。这样,就可以确保两个 bean 之间没有直接的依赖关系。
spring自带的三级缓存【使用代理】: (默认)使用 AOP 代理来实现 bean 之间的依赖关系。在编译时就解决循环依赖问题。
三级缓存的作用是 Spring IoC 的难点,搞清楚它的原理和背后的原因非常有必要!
前言
spring bean 的 创建三步:
-
创建bean类的实例
-
注入属性
-
bean对象的初始化
单例模式
一般在单例模式中,会使用双重检查锁定(Double-Checked Locking)来保证线程安全。双重检查锁定是为了避免多个线程同时进入临界区,以及提高性能。具体来说:
- 第一个检查是为了保证只有少量线程能够进入临界区。这种方式可以减少锁的竞争,提高性能。
- 第二个检查是为了保证在多个线程中只有一个线程能够创建实例。
这样可以避免(多个实例并行请求建立单例对象时,都卡在synchronized锁那里,当一个线程已经创建完实例后,)在实例已经存在的情况下,多个线程继续创建实例,从而浪费资源。
```
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
```
在上面的代码中,我们使用了双重检查锁定来确保只有一个实例被创建。首先,在第一个检查中,我们检查实例是否已经被创建。如果没有,我们进入同步块。在同步块中,我们再次检查实例是否已经被创建。如果没有,我们创建一个新的实例。由于我们使用了volatile关键字,这确保了在并发环境下的可见性和正确性。
源码
spring中bean 的创建是由 AbstractBeanFactory#getBean() 触发的。
循环依赖过程分析
AbstractBeanFactory#getBean() --> AbstractBeanFactory#doGetBean() --> DefaultSingletonBeanRegistry#getSingleton()
在触发 bean 的加载时,会先从缓存中获取 bean,也就是会调用 DefaultSingletonBeanRegistry#getSingleton() 方法。
跟一下 AbstractBeanFactory#getBean(java.lang.String) 的源码,会发现它会调用 DefaultSingletonBeanRegistry#getSingleton()
:
源码一
DefaultSingletonBeanRegistry#getSingleton()
从三级缓存中获取单例对象
默认的单例bean注册器 DefaultSingletonBeanRegistry#getSingleton()
~~~
// 参数allowEarlyReference:允许早期引用 【默认是true】:
protected Object getSingleton(String beanName, boolean
allowEarlyReference) {
// 从一级缓存中 快速检查有没有 完全初始化的现有实例
Object
singletonObject = this.
singletonObjects.get(beanName);
//
类似于单例模式的双锁校验。 一级缓存 中没有 beanName, 且 beanName 正在创建中
if (
singletonObject
== null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.
singletonObject) {
//现在 二级缓存中查
singletonObject = this.
earlySingletonObjects.get(beanName);
// 判断 等于null才处理,避免多个竞争锁的线程都走一遍创建
//(加判定后,都去读二级缓存就可以了),且允许早期引用时才继续
if (
singletonObject
== null &&
allowEarlyReference) {
// 先去三级缓存中 查询 ,三级缓存有时才创建!
ObjectFactory<?> singletonFactory = this.
singletonFactories.get(beanName);
if (
singletonFactory
!=
null) {
// 获取引用对象存于三级缓存中, 再存入 earlySingletonObjects,并返回此对
//象。 对应三级缓存中删除相应工厂类
singletonObject = singletonFactory.getObject();
this.
earlySingletonObjects.put(beanName,
singletonObject);
this.
singletonFactories.remove(beanName);
}
}
}
}
return
singletonObject;
}
~~~
以上就是三级缓存的代码。其中:
-
singletonObjects ** 一级缓存:
cache of singleton objects: bean name to bean instance.
用于存放已经完全初始化好的 bean
-
earlySingletonObjects ** 二级缓存:
cache of early singleton objects: bean name to bean instance.
用于存放
bean 的早期引用
(只实例化类,还没初始化的对象)(
已经创建,尚未完全初始化的单例对象)(循环依赖时才会存放并使用)
-
singletonFactories ** 三级缓存:
cache of singleton factories: bean name to ObjectFactory.
用于存放三级缓存 ObjectFactory 获取到的工厂对象(都会存放,循环依赖时才会使用)
批注
与 单例模式 同理,在产生循环依赖的问题时, 在二级缓存查询前就外就开始加了 synchronized的重量级锁 锁住了 singletonObject 对象,
当在并发的情况下,其他要 创建 bean /类的实例化 的线程都得处于等待的过程。
当一个线程创建完后,其他线程 判断 等于null才处理,避免多个竞争锁的线程都走一遍创建(加判定后,都去读二级缓存就可以。
这样确保只有一个线程 去走第三缓存并创建bean!
第三缓存,乍一看并没有什么特殊的意义,因为锁内的第二缓存已经线程安全的!可以用局部变量代替第三缓存(反正第三缓存用后即删了)。硬说与线程安全有和关联的话,可能是 和 三级缓存 singletonFactories 的注入/赋值有什么关联了,但此方法中并没有三级缓存的插入。三级缓存的插入在 同类下的方法中 DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)进行插入的。说明得确保先有对应的工厂类才会去处理并创建bean实例对象。
个人感觉 此处已经与 线程安全没有啥关系了,和spring的设计思路和思想有关吧~
此方法中没有三级缓存
singletonFactories 的数据来源,即数值的插入!数据来源在 同类 DefaultSingletonBeanRegistry 下的方法中 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
:
源码二
DefaultSingletonBeanRegistry#
addSingletonFactory
()
现在开始找找 三级缓存
singletonFactories
的数据来源 !!
/**
如有必要 添加给定的单例工厂以构建指定的单例。 要求对单例进行急切注册,例如能够解析循环引用。
参数singletonFactory 是 beanName和单例bean的工厂。
*/
这个类的调用在 AbstractAutowireCapableBeanFactory# doCreateBean()中
:
源码三
AbstractAutowireCapableBeanFactory
#
doCreateBean
()
在bean类的 实例化之前,就已经开始准备 bean提前引用的对象 了
// 中间代码忽略。。。。。。。
在 AbstractAutowireCapableBeanFactory# doCreateBean() 中可见:
-
实例化bean类 createBeanInstance(beanName, mbd, args);
-
。。。。。。
-
第三级缓存的数值来源准备, 调用了方法: getEarlyBeanReference(): 【源码四 见详情】
-
populateBaen 装填属性
-
获取 单例对象 getSingleton (查 一二三级缓存)
-
hasDependentBean(beanName) 查询是否有依赖的bean并处理
-
。。。。。
-
初始化 bean 实例/对象
源码四
AbstractAutowireCapableBeanFactory
#
getEarlyBeanReference
()
工厂类预先生成代理
getEarlyBeanReference方法,* 获取指定 bean 的早期引用,通常用于解析循环引用。
/**
* Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
获取引用以便提前访问指定的 Bean,通常用于解析循环引用。
* @param beanName – Bean 的名称(用于错误处理目的)。 the name of the bean (for error handling purposes)
* @param mbd – Bean 的合并 Bean 定义 . the merged bean definition for the bean
* @param bean - – 原始 Bean 实例 。 the raw bean instance
* return :要公开为 Bean 引用的对象。 the object to expose as bean reference
*/
protected Object
getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof
SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (
SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
这个工厂的作用就是判断这个对象是否需要代理,如果否 直接返回,如果需要 则创建代理对象 并返回。
这里还有个知识点:
Spring AOP : 自动代理创建机制 (APC)
一个APC其实是一个SmartInstantiationAwareBean
PostProcessor
, 它会介入每个bean的 类实例化 和 对象的初始
化
,检测bean的特征。如果该bean符合某些特征,比如有拦截器需要应用到该bean,那么该APC就会为它自动创建一个代理对象,使用相应的拦截器包裹住该bean。
循环依赖 流程图
在有循环依赖的情况下:(A --> B --> A 的场景)
A 第一次加载时,会将 A 对应的 ObjectFactory 放到三级缓存中;
当 B 创建完实例后,进行 populateBean 填充依赖时,会通过 getBean(A) 来获取 bean A,这时会使用 A 对应的三级缓存 ObjectFactory 来获取 bean A 的早期引用。
总结
问题 : spring为什么要用三级缓存?二级缓存不能实现么 ?
上述: 一级缓存 存 已经完全初始化的bean单例的对象,
二级缓存 存 类已实例化还初始化的不完全对象,
三级缓存 存的是创建类实例的工厂类对象(这一级缓存的时间还早于装填属性,在检测到循环依赖之前。)
在二级缓存没有时,再去获取这个工厂类对象,并将结果存于二级缓存也是可行的!但是:
正常的代理的对象初始化后期调用生成的,是基于后置处理器PostProcessor的,若提早的代理就违背了Bean定义的生命周期。所以spring在一个三级缓存放置一个工厂,如果产生循环依赖 ,那么就会调用这个工厂提早的得到代理的对象。