一、前言 这一篇博文主要讲一下我们spring是怎么解决循环依赖的问题的。 二、什么是循环依赖 首先我们需要明确,什么是循环依赖呢?这里举一个简单的例子: @Service public class A { @Aut…
一、前言 这一篇博文主要讲一下我们spring是怎么解决循环依赖的问题的。
二、什么是循环依赖 首先我们需要明确,什么是循环依赖呢?这里举一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 @Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; } 复制代码
以这个例子来看,我们声明了a、b两个bean,且a中需要注入一个b,b中需要注入一个a。 结合我们上篇博文的bean生命周期的知识,我们来模拟一下这两个bean创建的流程:
如果没有缓存的设计,我们的虚线所示的分支将永远无法到达,导致出现无法解决的循环依赖问题….
三、三级缓存设计 1. 自己解决循环依赖问题 现在,假如我们是spring的架构师,我们应该怎么解决这个循环依赖问题呢? 1.1. 流程设计 首先如果要解决这个问题,我们的目标应该是要把之前的级联的无限创建流程切到,也就是说我们的流程要变为如下所示:
也就是说,我们需要在B实例创建后,注入A的时候,能够拿到A的实例,这样才能打破无限创建实例的情况。 而B实例的初始化流程,是在A实例创建之后,在populateBean方法中进行依赖注入时触发的。那么如果我们B实例化过程中,想要拿到A的实例,那么A实例必须在createBeanInstance创建实例后(实例都没有就啥也别说了)、populateBean方法调用之前,就暴露出去,让B能通过getBean获取到!(同学们认真想一下这个流程,在现有的流程下改造,是不是只能够这样操作?自己先想清楚这个流程,再去结合spring源码验证,这一块的知识点你以后想忘都忘不掉) 那么结合我们的思路,我们再修改一下流程图:
1.2. 伪代码实现 流程已经设计好了,那么我们其实也可以出一下这个流程的伪代码(伪代码就不写加锁那些流程了):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private Map<String, Object> singleMap = new ConcurrentHashMap <>(16 );private Map<String, Object> cacheMap = new ConcurrentHashMap <>(16 );protected Object getBean (final String beanName) { Object single = singleMap.get(beanName); if (single != null ) { return single; } single = cacheMap.get(beanName); if (single != null ) { return single; } Object beanInstance = createBeanInstance(beanName); cacheMap.put(beanName, beanInstance); populateBean(beanName, beanInstance); initializeBean(beanName, beanInstance); singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) return beanInstance; } 复制代码
可以看到,如果我们自己实现一个缓存结构来解决循环依赖的问题的话,可能只需要两层结构就可以了,但是spring却使用了3级缓存,它有哪些不一样的考量呢?
2. Spring源码 我们已经知道该怎么解决循环依赖问题了,那么现在我们就一起看一下spring源码,看一下我们的分析是否正确。 由于之前我们已经详细讲过整个bean的生命周期了,所以这里就只挑三级缓存相关的代码段来讲了,会跳过比较多的代码,同学们如果有点懵,可���温��一下万字长文讲透bean的生命周期。 2.1. Spring的三级缓存设计 2.1.1. 三级缓存源码 首先,在我们的AbstractBeanFactory#doGetBean的逻辑中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public Object getBean (String name) throws BeansException { return doGetBean(name, null , null , false ); } 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; Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null ) { bean = getObjectForBeanInstance(sharedInstance, name, beanName, null ); } else { if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } } return (T) bean; } 复制代码
可以看到,如果我们使用getSingleton(beanName)直接获取到bean实例了,是会直接把bean实例返回的,我们一起看一下这个方法(这个方法属于 DefaultSingletonBeanRegistry):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 );private final Map<String, Object> earlySingletonObjects = new HashMap <>(16 );private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap <>(16 );public Object getSingleton (String beanName) { return getSingleton(beanName, true ); } 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; } 复制代码
一二级缓存都好理解,其实就可以理解为我们伪代码里面的那两个Map,但是这个三级缓存是怎么回事?ObjectFactory又是个什么东西?我们就先看一下这个ObjectFactory的结构:
1 2 3 4 5 6 @FunctionalInterface public interface ObjectFactory <T> { T getObject () throws BeansException; } 复制代码
我们回到这个三级缓存的结构,二级缓存是是在getSingleton方法中put进去的,这跟我们之前分析的,创建bean实例之后放入,好像不太一样?那我们是不是可以推断一下,其实创建bean实例之后,是放入三级缓存的呢(总之实例创建之后是需要放入缓存的)?我们来跟一下bean实例化的代码,主要看一下上一篇时刻意忽略掉的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 protected Object doCreateBean (final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = instanceWrapper.getWrappedInstance(); applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { throw new BeanCreationException (...); } if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } } } try { registerDisposableBeanIfNecessary(beanName, bean, mbd); } catch (BeanDefinitionValidationException ex) { throw new BeanCreationException (...); } return exposedObject; } 复制代码
可以看到,初始化一个bean是,创建bean实例之后,如果这个bean是单例bean&&允许循环依赖&&当前bean正在创建,那么将会调用addSingletonFactory加入三级缓存:
1 2 3 4 5 6 7 8 9 10 11 protected void addSingletonFactory (String beanName, ObjectFactory<?> singletonFactory) { synchronized (this .singletonObjects) { if (!this .singletonObjects.containsKey(beanName)) { this .singletonFactories.put(beanName, singletonFactory); this .earlySingletonObjects.remove(beanName); this .registeredSingletons.add(beanName); } } } 复制代码
也就是说我们伪代码中的这一段有了:
1 2 3 4 5 6 7 Object beanInstance = createBeanInstance(beanName);cacheMap.put(beanName, beanInstance); 复制代码
那么接下来,完全实例化完成的bean又是什么时候塞入我们的实例Map(一级缓存)singletonObjects的呢? 这个时候我们就要回到调用createBean方法的这一块的逻辑了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } 复制代码
可以看到,我们的createBean创建逻辑是通过一个lamdba语法传入getSingleton方法了,我们进入这个方法看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public Object getSingleton (String beanName, ObjectFactory<?> singletonFactory) { synchronized (this .singletonObjects) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null ) { beforeSingletonCreation(beanName); boolean newSingleton = false ; try { singletonObject = singletonFactory.getObject(); newSingleton = true ; } catch (IllegalStateException ex) { singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null ) { throw ex; } } catch (BeanCreationException ex) { throw ex; } finally { afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } } 复制代码
哈哈,加入实例Map(一级缓存)singletonObjects的逻辑明显就是在这个addSingleton中了:
1 2 3 4 5 6 7 8 9 10 11 12 protected void addSingleton (String beanName, Object singletonObject) { synchronized (this .singletonObjects) { this .singletonObjects.put(beanName, singletonObject); this .singletonFactories.remove(beanName); this .earlySingletonObjects.remove(beanName); this .registeredSingletons.add(beanName); } } 复制代码
也就是说,我们伪代码的这一块在spring里面也有对应的体现,完美:
1 2 3 4 5 6 initializeBean(beanName, beanInstance); singleMap.put(beanName, beanInstance); cacheMap.remove(beanName) 复制代码
就这样,spring通过缓存设计解决了循环依赖的问题。 2.1.2. 三级缓存解决循环依赖流程图 什么,看完代码之后还是有点模糊?那么把我们的流程图再改一下,按照spring的流程来:
2.1.3. 三级缓存解决循环依赖伪代码 看完图还觉得不清晰的话,我们把所有spring中三级缓存相关的代码汇总到一起,用伪代码的方式,拍平成一个方法,大家应该感觉会更清晰了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 private final Map<String, Object> singletonObjects = new ConcurrentHashMap <>(256 );private final Map<String, Object> earlySingletonObjects = new HashMap <>(16 );private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap <>(16 );protected Object getBean (final String beanName) { Object single = singletonObjects.get(beanName); if (single != null ) { return single; } single = earlySingletonObjects.get(beanName); if (single != null ) { return single; } ObjectFactory<?> objectFactory = singletonFactories.get(beanName); if (objectFactory != null ) { single = objectFactory.get(); earlySingletonObjects.put(beanName, single); singletonFactories.remove(beanName); return single; } Object beanInstance = createBeanInstance(beanName); singletonFactories.put(beanName, () -> return beanInstance); populateBean(beanName, beanInstance); initializeBean(beanName, beanInstance); Object earlySingletonReference = earlySingletonObjects.get(beanName); if (earlySingletonReference != null ) { beanInstance = earlySingletonObject; } singletonObjects.put(beanName, beanInstance); earlySingletonObjects.remove(beanName); singletonFactories.remove(beanName); return beanInstance; } 复制代码
把所有逻辑放到一起之后会清晰很多,同学们只需要自行模拟一遍,再populateBean中再次调用getBean逻辑进行依赖注入,应该就能捋清楚了。 2.1.4. 标记当前bean正在创建 在我们刚刚看到的将bean实例封装成ObjectFactory并放入三级缓存的流程中,有一个判断是当前bean是正在创建,这个状态又是怎么判断的呢:
1 2 3 4 5 6 7 8 boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } 复制代码
我们看一下这个 isSingletonCurrentlyInCreation的逻辑:
1 2 3 4 5 6 private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap <>(16 )); public boolean isSingletonCurrentlyInCreation (String beanName) { return this .singletonsCurrentlyInCreation.contains(beanName); } 复制代码
可以看到额,其实就是判断当前beanName是不是在这个 singletonsCurrentlyInCreation容器中,那么这个容器中的值又是什么时候操作的呢? 希望同学们还记得getSingleton(beanName, singletonFactory)中有调用的beforeSingletonCreation和afterSingletonCreation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public Object getSingleton (String beanName, ObjectFactory<?> singletonFactory) { synchronized (this .singletonObjects) { Object singletonObject = this .singletonObjects.get(beanName); if (singletonObject == null ) { beforeSingletonCreation(beanName); boolean newSingleton = false ; try { singletonObject = singletonFactory.getObject(); newSingleton = true ; } catch (BeanCreationException ex) { throw ex; } finally { afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } } 复制代码
我们现在来看一下这两个方法的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected void beforeSingletonCreation (String beanName) { if (!this .inCreationCheckExclusions.contains(beanName) && !this .singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException (beanName); } } protected void afterSingletonCreation (String beanName) { if (!this .inCreationCheckExclusions.contains(beanName) && !this .singletonsCurrentlyInCreation.remove(beanName)) { throw new IllegalStateException ("Singleton '" + beanName + "' isn't currently in creation" ); } } 复制代码
可以看到,我们这两个方法主要就是对 singletonsCurrentlyInCreation容器进行操作的,inCreationCheckExclusions这个容器可以不用管它,这名称一看就是一些白名单之类的配置。 这里需要主要的是beforeSingletonCreation中,如果 singletonsCurrentlyInCreation.add(beanName)失败的话,是会抛出BeanCurrentlyInCreationException的,这代表spring遇到了无法解决的循环依赖问题,此时会抛出异常中断初始化流程,毕竟单例的bean不允许被创建两次。 2.2. 为什么要设计为三级结构? 2.2.1. 只做两级缓存会有什么问题? 其实到这里,我们已经清楚,三级缓存的设计已经成功地解决了循环依赖的问题。 可是按我们自己的设计思路,明明只需要两级缓存就可以解决,spring却使用了三级缓存,难道是为了炫技么? 这个时候,就需要我们再细致地看一下bean初始化过程了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 protected Object doCreateBean (final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } Object exposedObject = bean; try { populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); } return exposedObject; } 复制代码
仔细观察,initializeBean方法是可能返回一个新的对象,从而把createBeanInstance创建的bean实例替换掉的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 protected Object initializeBean (final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { invokeAwareMethods(beanName, bean); Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException (...); } if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; } 复制代码
可以看到,我们的 postProcessBeforeInitialization和postProcessAfterInitialization的埋点方法都是有可能把我们的bean替换掉的。 那么结合整个流程来看,由于我们放入缓存之后,initializeBean方法中可能存在替换bean的情况,如果只有两级缓存的话:
这会导致B中注入的A实例与singletonObjects中保存的AA实例不一致,而之后其他的实例注入a时,却会拿到singletonObjects中的AA实例,这样肯定是不符合预期的。 2.2.2. 三级缓存是如何解决问题的 那么这个问题应该怎么解决呢? 这个时候我们就要回到添加三级缓存的地方看一下了。addSingletonFactory的第二个参数就是一个ObjectFactory,并且这个ObjectFactory最终将会放入三级缓存,现在我们再回头看调用addSingletonFactory的地方:
1 2 3 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 复制代码
熟悉lamdba语法的同学都知道,getEarlyBeanReference其实就是放入三级缓存中的ObjectFactory的getObject方法的逻辑了,那我们一起来看一下,这个方法是做了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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; } 复制代码
咦,这里也有个埋点,可以替换掉bean的引用。 原来为了解决initializeBean可能替换bean引用的问题,spring就设计了这个三级缓存,他在第三级里保存了一个ObjectFactory,其实具体就是getEarlyBeanReference的调用,其中提供了一个getEarlyBeanReference的埋点方法,通过这个埋点方法,它允许开发人员把需要替换的bean,提早替换出来。 比如说如果在initializeBean方法中希望把A换成AA(这个逻辑肯定是通过某个beanPostProcessor来做的),那么你这个beanPostProcessor可以同时提供getEarlyBeanReference方法,在出现循环依赖的时候,可以提前把A->AA这个逻辑做了,并且initializeBean方法不再做这个A->AA的逻辑,并且,当我们的循环依赖逻辑走完,A创建->注入B->触发B初始化->注入A->执行缓存逻辑获取AA实例并放入二级缓存->B初始化完成->回到A的初始化逻辑时,通过以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 protected Object doCreateBean (...) { populateBean(beanName, mbd, instanceWrapper); Object exposedObject = initializeBean(beanName, exposedObject, mbd); if (earlySingletonExposure) { Object earlySingletonReference = getSingleton(beanName, false ); if (earlySingletonReference != null ) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } } } return exposedObject; } 复制代码
这样就能保证当前bean中注入的AA和singletonObjects中的AA实例是同一个对象了。 将会把二级缓存中的AA直接返回,这时就能保证B中注入的AA实例与spring管理起来的最终的AA实例是同一个了。 整个流程梳理一下就是这样:
2.2.3. 三级缓存的实际应用 既然设计了这个三级缓存,那么肯定是有实际需求的,我们上面分析了一大堆,现在正好举一个例子看一下,为什么spring需要三级缓存。 我们都知道,Spring的AOP功能,是通过生成动态代理类来实现的,而最后我们使用的也都是代理类实例而不是原始类实例。而AOP代理类的创建,就是在initializeBean方法的 postProcessAfterInitialization埋点中,我们直接看一下getEarlyBeanReference和postProcessAfterInitialization这两个埋点吧(具体类是AbstractAutoProxyCreator,之后讲AOP的时候会细讲):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor , BeanFactoryAware { private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap <>(16 ); public Object getEarlyBeanReference (Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); this .earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } public Object postProcessAfterInitialization (Object bean, String beanName) { if (bean != null ) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this .earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } } 复制代码
就这样,Spring巧妙地使用三级缓存来解决了这个不同实例的问题。当然,如果我们需要自己开发类似代理之类的可能改变bean引用的功能时,也需要遵循getEarlyBeanReference方法的埋点逻辑,学习AbstractAutoProxyCreator中的方式,才能让spring按照我们的预期来工作。
四、三级缓存无法解决的问题 1. 构造器循环依赖 刚刚讲了很多三级缓存的实现,以及它是怎么解决循环依赖的问题的。 但是,是不是使用了三级缓存,就能解决所有的循环依赖问题呢? 当然不是的,有一个特殊的循环依赖,由于java语言特性的原因,是永远无法解决的,那就是构造器循环依赖。 比如以下两个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class A { private final B b; public A (final B b) { this .b = b; } } public class B { private final A a; public B (final A a) { this .a = a; } } 复制代码
抛开Spring来讲,同学们你们有办法让这两个类实例化成功么? 该不会有同学说,这有何难看我的:
1 2 3 final A a = new A (new B (a));复制代码
不好意思,这个真的不行,不信可以去试试。从语法上来讲,java的语言特性决定了不允许使用未初始化完成的变量。我们只能无限套娃:
1 2 3 final A a = new A (new B (new A (new B (new A (new B (...))))));复制代码
所以,连我们都无法解决的问题,就不应该强求spring来解决了吧~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Service public class A { private final B b; public A (final B b) { this .b = b; } } @Service public class B { private final A a; public B (final A a) { this .a = a; } } 复制代码
启动之后,果然报错了:
1 2 Error creating bean with name 'a' : Requested bean is currently in creation: Is there an unresolvable circular reference? 复制代码
2. Spring真的对构造器循环依赖束手无策么? 难道,spring对于这种循环依赖真的束手无策了么?其实不是的,spring还有@Lazy这个大杀器…只需要我们对刚刚那两个类小小的改造一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service public class A { private final B b; public A (final B b) { this .b = b; } public void prt () { System.out.println("in a prt" ); } } @Service public class B { private final A a; public B (@Lazy final A a) { this .a = a; } public void prt () { a.prt(); } } @Test public void test () { applicationContext = new ClassPathXmlApplicationContext ("spring.xml" ); B bean = applicationContext.getBean(B.class); bean.prt(); } 复制代码
都说了成功了,运行结果同学们也能猜到了吧:
(同学们也可以自己尝试一下~
3. @Lazy原理 这个时候我们必须要想一下,spring是怎么通过 @Lazy来绕过我们刚刚解决不了的无限套娃问题了。 因为这里涉及到之前没有细讲的参数注入时候的参数解析问题,我这边就不带大家从入口处一步一步深入了,这边直接空降到目标代码 DefaultListableBeanFactory#resolveDependency:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Object resolveDependency (DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null ) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } 复制代码
我们直接看一下这个 getLazyResolutionProxyIfNecessary,这个方法就是获取LazyProxy的地方了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver { @Override @Nullable public Object getLazyResolutionProxyIfNecessary (DependencyDescriptor descriptor, @Nullable String beanName) { return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null ); } protected boolean isLazy (DependencyDescriptor descriptor) { for (Annotation ann : descriptor.getAnnotations()) { Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class); if (lazy != null && lazy.value()) { return true ; } } MethodParameter methodParam = descriptor.getMethodParameter(); if (methodParam != null ) { Method method = methodParam.getMethod(); if (method == null || void .class == method.getReturnType()) { Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class); if (lazy != null && lazy.value()) { return true ; } } } return false ; } protected Object buildLazyResolutionProxy (final DependencyDescriptor descriptor, final @Nullable String beanName) { final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); TargetSource ts = new TargetSource () { @Override public Class<?> getTargetClass() { return descriptor.getDependencyType(); } @Override public boolean isStatic () { return false ; } @Override public Object getTarget () { Object target = beanFactory.doResolveDependency(descriptor, beanName, null , null ); if (target == null ) { Class<?> type = getTargetClass(); if (Map.class == type) { return Collections.emptyMap(); } else if (List.class == type) { return Collections.emptyList(); } else if (Set.class == type || Collection.class == type) { return Collections.emptySet(); } throw new NoSuchBeanDefinitionException (...); } return target; } @Override public void releaseTarget (Object target) { } }; ProxyFactory pf = new ProxyFactory (); pf.setTargetSource(ts); Class<?> dependencyType = descriptor.getDependencyType(); if (dependencyType.isInterface()) { pf.addInterface(dependencyType); } return pf.getProxy(beanFactory.getBeanClassLoader()); } } 复制代码
同学们可能对TargetSource和ProxyFactory这些不熟悉,没关系,这不妨碍我们理解逻辑。 从源码我们可以看到,对于@Lazy的依赖,我们其实是返回了一个代理类(以下称为LazyProxy)而不是正真通过getBean拿到目标bean注入。而真正地获取bean的逻辑,被封装到了一个TargetSource类的getTarget方法中,而这个TargetSource类最终被用来生成LazyProxy了,那么我们是不是可以推测,LazyProxy应该持有这个TargetSource对象。 而从我们懒加载的语意来讲,是说真正使用到这个bean(调用这个bean的某个方法时)的时候,才对这个属性进行注入/初始化。 那么对于当前这个例子来讲,就是说其实B创建的时候,并没有去调用getBean(“a”)去获取构造器的参数,而是直接生成了一个LazyProxy来做B构造器的参数,而B之后正真调用到A的方法时,才会去调用TargetSource中的getTarget获取A实例,即调用getBean(“a”),这个时候A早就实例化好了,所以也就不会有循环依赖问题了。
4. 伪代码描述 还是同样,我们可以用伪代码来描述一下这个流程,伪代码我们就直接用静态代理来描述了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class A { private final B b; public A (final B b) { this .b = b; } public void prt () { System.out.println("in a prt" ); } } public class B { private final A a; public B (final A a) { this .a = a; } public void prt () { a.prt(); } } public class LazyProxyA extends A { private A source; private final Map<String, Object> ioc; private final String beanName; public LazyProxyA (Map<String, Object> ioc, String beanName) { super (null ); this .ioc = ioc; this .beanName = beanName; } @Override public void prt () { if (source == null ) { source = (A) ioc.get(beanName); } source.prt(); } } 复制代码
那么整个初始化的流程简单来描述就是:
1 2 3 4 5 6 7 8 Map<String, Object> ioc = new HashMap <>(); void init () { B b = new B (new LazyProxyA (ioc, "a" )); ioc.put("b" , b); A a = new A ((B)ioc.get("b" )); ioc.put("a" , a); } 复制代码
我们也模拟一下运行:
1 2 3 4 5 6 7 void test () { init(); B b = (B)ioc.get("b" ); b.prt(); } 复制代码
当然是能成功打印的:
六、总结 关于循环依赖的问题,Spring提供了通过设计缓存的方式来解决的,而设计为三级缓存,主要是为了解决bean初始化过程中,实例被放入缓存之后,实例的引用还可能在调用initializeBean方法时被替换的问题。 对于构造器的循环依赖,三级缓存设计是无法解决的,这属于java语言的约束;但是spring提供了一种使用@Lazy的方式,绕过这个限制,使得构造器的循环依赖在特定情况下(循环链中的某个注入打上@Lazy注解)也能解决。 (小声BB,接下来更新应该会变慢了…
最后,小编还整理了面试指南大全,有需要的添加小助理vx:mxzFAFAFA即可!!
本文标题: 逐行解读Spring- 没人比我更懂循环依赖
本文作者: OSChina
发布时间: 2021年04月15日 10:16
最后更新: 2025年04月03日 11:07
原始链接: https://haoxiang.eu.org/c7d95772/
版权声明: 本文著作权归作者所有,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!