我們來探討一下Spring是如何解決循環依賴問題的。
什麼是循環依賴
先看一個示例圖吧:
好像很抽象的樣子,沒事,直接看代碼就很清晰了:
class A{
private B b;
}
class B{
private C c;
}
class C{
private A a;
}
大概就是這麼一種依賴結構,對應的配置文件如下:
<bean id="beanA" class="com.jay.bean.A">
<property name="b" ref="beanB"/>
</bean>
<bean id="beanB" class="com.jay.bean.B">
<property name="c" ref="beanC"/>
</bean>
<bean id="beanC" class="com.jay.bean.C>
<property name="a" ref="beanA"/>
</bean>
也就是這樣了。
我這樣模擬了一個:
public class StudentServiceImpl implements StudentService, BeanFactoryPostProcessor {
int id;
String name;
Den den;
public StudentServiceImpl() {
}
}
//
public class Den {
StudentService studentService;
public StudentService getStudentService() {
return studentService;
}
public Den() {
}
}
public class StudentTest {
public static void main(String[] args){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
StudentService studentService=(StudentService)applicationContext.getBean("studentService");
studentService.study();
}
}
我們都知道Spring IOC在創建完實例之後,需要對實例進行屬性填充,而此時如果發現了循環依賴的關係,會怎麼辦呢?
IOC使用是一種 允許未完成初始化的對象提前發佈 的一種思想來解決的,具體的實現呢,就是使用了三級緩存
三級緩存
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
IOC中的三級緩存其實就是三個Map,並且這三個都是存放在DefaultListableBeanFactory這個工廠類對象中,DefaultListableBeanFactory應該不需要多解釋了。
- singletonObjects:一級緩存是一個ConcurrentHashMap,存放着對象實例,我們調用getBean方法其實都是從這個map中直接進行獲取的
- earlySingletonObjects:二級緩存是一個HashMap,也是存放着對象實例,但和一級緩存的區別就是,二級緩存存放的是尚未經過初始化的實例,也就是還未進行屬性賦值的實例
- singletonFactories:三級緩存也是一個HashMap,存放着對應bean的工廠類對象
流程
上面的代碼寫了三個對象的相互依賴,這裏爲了簡便起見,我們只討論兩個對象的的依賴情況。
經過一系列流程,此時容器完成了初始化,配置文件也被解析成了beanDefinition對象,並且註冊到了beanFactory中,此時要做的,就是按照beanDefinition,開始進行bean的實例化了
對一個bean的實例化,從getBean方法開始,而getBean方法,會調用 doGetBean():
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// 調用getSingleton方法,這裏最終會去一級緩存中獲取,自然也只能獲取到空值了。
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
//獲取到空值,那麼就要進行實例化了
else {
...
try {
...
// 如果是單例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
//這裏纔是真正實例化的地方
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
...
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
...
return (T) bean;
}
那就看一下createBean()吧:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
if (logger.isDebugEnabled()) {
logger.debug("Creating instance of bean '" + beanName + "'");
}
//獲取該bean的模板,也就是beanDefinition實例
RootBeanDefinition mbdToUse = mbd;
// Make sure bean class is actually resolved at this point, and
// clone the bean definition in case of a dynamically resolved Class
// which cannot be stored in the shared merged bean definition.
Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
// Prepare method overrides.
try {
mbdToUse.prepareMethodOverrides();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(),
beanName, "Validation of method overrides failed", ex);
}
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
catch (Throwable ex) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
"BeanPostProcessor before instantiation of bean failed", ex);
}
try {
//重點是這裏
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException ex) {
// A previously detected exception with proper bean creation context already...
throw ex;
}
catch (ImplicitlyAppearedSingletonException ex) {
// An IllegalStateException to be communicated up to DefaultSingletonBeanRegistry...
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(
mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
}
}
看看doGetBean裏:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//這一步纔是真正地,將bean對象實例化了,並且存放在instanceWrapper這個包裝類對象中
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//從包裝類對象中獲取到bean實例
final Object bean = instanceWrapper.getWrappedInstance(); //1
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
// Allow post-processors to modify the merged bean definition.
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true;
}
}
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//寫入三級緩存,這是實現循環依賴的關鍵!
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
//進行初始化,也就是屬性填充
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
throw (BeanCreationException) ex;
}
else {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
}
}
...
...
return exposedObject;
}
現在bean實例已經完成了,按理來說我們可以直接返回了,但事實上,我們還需要寫緩存呢,因爲要解決依賴問題,所以接下來就看看addSingletonFactory方法裏面做了什麼:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
做了這麼幾件事:
1.寫三級緩存。(看看三級緩存裏面都有什麼):
看singletonFactories屬性中,已經存入了一個工廠類對象,裏面存儲着一個StudentServiceImpl@2067,正是我們剛剛實例化完成,但還沒有進行賦值的那個對象
2.從二級緩存中移除(當然二級緩存中目前什麼也沒有)
接下來就是要進行屬性注入了,其中也要注入den屬性,這裏就發生循環依賴了,populateBean方法就不仔細看了,裏面就是會循環bean實例裏面的參數進行賦值,當循環到den屬性時,發現是一個對象,那麼就要進行den的初始化了。在一級二級三級緩存都沒有發現den對象,那麼就要對den進行初始化了,那den的初始化和StudentServiceImpl初始化類似,都是調用:
getBean — > doGetBean —> getSingleton這樣子 ,獲取到den的實例(還沒有初始化噢!),同樣也要註冊到三級緩存中。同樣,也要進行賦值,在賦值時,因爲den也依賴了studentServiceImpl,所以它同樣要給studentServiceImpl賦值,同樣要去一級二級三級緩存中查找,而這次不同的是,它會在三級緩存中找到studentServiceImpl對象對應的工廠類對象,於是就能獲取到了studentServiceImpl對象,而這個studentServiceImpl對象正是我們剛剛創建的那個,獲取到它之後,賦值給den,那麼den的實例化就算是完成了,雖然den鎖依賴的studentServiceImpl對象還沒有完成初始化,也就是它內部的屬性都是一個原始值,但是沒關係,den對象完成了實例化就足夠了
去一級二級三級緩存查找的過程如下:
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) {
//singletonFactories已經有一個值了,在創建studentServiceImpl實例(未初始化)時就已經經過一次
//addSingletonFactory操作了,將“早期引用”存放在這裏了,所以這裏直接獲取到了
//取出該“早期引用”,裏面是一個studentServiceImpl實例,但屬性都是原始值
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
//返回
return singletonObject;
}
!
den初始化完成了,然後就是回到studentServiceImpl的賦值階段了唄,den已經有了,直接賦值,ok,至此,studentServiceImpl對象的創建就已經完成了,後續就是將該對象存到一級緩存中,使得後續調用getBean可以直接在一級緩存中獲取該對象。ok,那麼這樣的話,對象的創建就大功告成啦!循環依賴的問題也解決了。
最後再以一道面試題來總結下博客的內容吧~
面試官:IOC是如何解決循環依賴的問題的呢?
A:IOC解決循環依賴的思想就是:將初始化還未完成的對象提前發佈。
對應的實現呢,就是ioc中的beanFactory維護了三個級別的緩存
- 一級緩存叫做singletonObjects,它是一個ConcurrentHashMap,它存的value就是bean對象實例了,而後續調用getBean方法其實也是從這個map裏獲取
- 二級緩存叫做earlySingletonObjects,它是一個HashMap,而它存的也是bean對象實例,但和一級緩存的區別就是,二級緩存存的實例還未進行初始化,也就是所有屬性值都只是數據類型的初始值。
- 三級緩存叫做singletonFactories,它也是一個HashMap,存的是bean的工廠類。
假設有兩個類是A,B是互相依賴的,那麼首先要對A類進行實例化。實例化的入口就是getBean這個方法,對應的操作就是以該bean的beanDefinition對象作爲模板,進行實例化,實例化之後,將該對象包裝成工廠類,註冊到三級緩存之中,而這一步就是實現循環依賴的一個關鍵所在了。進行了實例化之後,再對對象進行初始化,也就是屬性賦值,當賦值到B對象,卻發現一級二級三級緩存中都沒有B對象的值時,此時就要去創建B對象,同樣需要實例化,然後初始化的過程,那B對象的初始化過程也需要給A賦值,然後就去查找,最終在三級緩存中獲取到了對象A,然後賦值給對象B,並且將三級緩存的數據添加到二級緩存後進行清楚。至此B對象就已經實例化完畢了,雖然B對象中的A對象只是進行實例化,還未進行初始化,但B對象能夠成功實例化,就已經足夠了。B對象實例化完之後,就可以賦值給A對象了,然後A對象完成初始化,註冊在一級緩存中,那麼整個過程就完成了。也解決了互相依賴的問題。