Spring循环依赖详解

24 篇文章 17 订阅
22 篇文章 11 订阅

什么是循环依赖

今天这边来聊下spring中的循环依赖,在spring的bean生命周期中,循环依赖处于bean的依赖注入模块,循环依赖相信就算没有研究过spring源码的也经常听说,那么什么是循环依赖呢?我们抛开spring这个框架来聊下什么是循环依赖,循环依赖可能在我们平时的开发过程中是属于比较常见的,但是如果只是简简单单的java普通对象,循环依赖对于我们来说也是非常常见,也是非常简单的,普通对象的循环依赖也不需要怎么去处理,类似于在对象A中有个属性B,而在B中也有一个属性A,那么在普通的java对象中,完全可以这样:

A a= new A();
B b= new B();
a.b =b;
b.a =a;

所以基于上面的这种普通的java对象,我们当然可以手动的去赋值,那么这种从概念上是属于循环依赖,但其实处理起来也非常简单;再回到spring中,spring容器最大的功能就是对bean的生命周期进行管理,每个bean在创建的过程中,需要得到一个完整的bean需要对bean的所有属性进行赋值,如果两个bean出现了相互依赖的情况,那么如果spring没有处理循环依赖,那么出现的结果就是在bean的创建过程中出现相互依赖,导致这个bean永远无法创建出来,则就导致一直在相互创建,最终出现的结果就是出现Stack Overflow异常,也就是栈溢出,类似于一个递归一样的。
spring在创建bean的时候,过程如下:
1.实例化前;
2.实例化bean(推断构造);
3.实例化后;
4.填充属性;
5.初始化前;
6.初始后;
上面的这些过程在第4步就会对bean进行属性填充,也就是依赖注入,依赖注入就会将bean中的所有属性进行填充,填充的时候如果发现了循环依赖,那么就会进行循环依赖的处理,如果spring没有处理循环依赖,那么出现的结果如下:
在这里插入图片描述
这样就会出现了一个闭环,程序用于无法结束,就会出现Stack Overflow异常。

spring是如何解决循环依赖

那么我们知道了循环依赖产生的原因,那么spring是如何来解决循环依赖的呢?
我们先来看下面的程序:

@Component
public class AService {
   
   @Autowired
   private BService bService;
}
@Component
public class BService {

   @Autowired
   private AService aService;
}

这两个bean在spring中是完全可以创建出来的,不会报错的, 因为spring是会处理循环依赖的;spring处理循环依赖相信大家在网上都已经看了不少文章了,都是说的用的三级缓存处理的,但是都是很片面的,我也看了不少文章,但是都不能给我解惑,我这里大概简单介绍下spring在处理循环依赖过程中所涉及到一些知识点;spring 的bean在创建bean的过程中会涉及到几个集合
1.singltonOjects:一级缓存(单例池)
2.earlySingltonObjects:二级缓存
3.singltonFactories:三级缓存
4.singletonsCurrentlyInCreation:存放正在创建bean的set集合,存放的是正在创建的bean的名字
一级缓存:一级缓存就是我们常常说的spring的单例容器,spring依赖注入的bean都是从这个单例池中去获取的,创建后完整的bean也是存放在这个单例池中的,我们这里叫它为一级缓存;

二级缓存:二级缓存从名字上可以知道是叫做早期的单例对象,就是说它是存放的是早期的单例对象,可能是不完整的;

三级缓存:存放的是我们的实例化后的原始对象,就是存放一个刚刚被创建出来的原始对象。

我们这里大概说下这三个缓存之间的关系,首先我们的一个对象被创建出来过后会放入三级缓存中,移除二级缓存,三级缓存存放的是beanName对应的一个lambad表达式,这个表达式封装了刚刚实例化出来的对象、bean对应的BeanDefinition、beanName封装在一个ObjectFactory中,所以这个时候存放到三级缓存中的只是一个lambad表达式,当调用ObjectFactory的getObject的时候才会去执行lambad表达式,也就是方法的调用;
如果整个过程中没有出现循环依赖,那么永远不会使用到二级缓存,而且三级缓存也只是存放了实例化后封装的对象,也不会去使用;
当出现了循环依赖的时候,那么这个时候就会启用二级和三级缓存,首先从三级缓存取出对象,然后执行getObject,将返回的对象(如果出现了循环依赖,而又开启了aop,返回的是一个代理对象)放入二级缓存,然后移除三级缓存;
到这里可以得到一个知识点就是二级缓存和三级缓存是一对儿存在的,就是一个对象要么在二级缓存,要么在三级缓存,他们两个是成对出现的,存入二级缓存的时候,移除三级缓存,存入三级缓存的时候,移除二级缓存;说了这么多,可能不是很清楚它原理的会看的晕乎乎的,下面我们根据一个例子进行分析,bean的生命周期的一些过程我就省了,比如这里有两个Bean,AService和BService

@Component
public class AService {
   
   @Autowired
   private BService bS;
}
@Component
public class BService {

   @Autowired
   private AService aS;
}

上面这两个Bean是相互依赖的,也就是循环依赖的,加入spring首先扫描到是AService,那么AService的过程如下:
AService创建过程:
1.实例化AService,得到AService的原始对象 aS,将实例化后的对象aS放入—>三级缓存
2.填充aS中的bS->去容器找bS->找不到->进入创建BService的流程—>将返回的bS对象复制给AService中的bS;
3.填充其他属性;
4.初始化bean;
5.放入一级缓存单例池。
7.移除bS在二级、三级缓存中的对象。

BService创建过程:
1.实例化BService,得到BService的原始对象 bS,将实例化后的对象bS放入—>三级缓存;
2.填充bS中的aS->去容器找aS->判断as是否正在被创建—>从三级缓存获取aS—>调用三级缓存获取的lambad表达式(当出现了循环依赖,如果bean启用了循环依赖,这里调用lambad会执行aop,返回一个代理对象)—>将得到的对象放入二级缓存—>移除三级缓存–>返回A的实例对象–>赋值给BService 中的aS;
3.填充其他属性;
4.初始化bean;
5.放入一级缓存单例池(返回bS给Aservice的第二步)。
6.移除bS在二级、三级缓存中的对象。

当AService创建完成过后,然后去创建BService,发现BService已经被创建了,因为创建Aservice的时候出现了循环依赖,就已经去创建了BService,所以这样就处理完成了。

所以上面就是spring处理循环依赖的一个大致过程,那么思考一下spring会什么要三个缓存,具体说是为什么需要二级和三级缓存,二级缓存是存放了原始对象,而三级缓存是存放的是可以aop过后的对象或者改变过后的对象,我们上面说的是AService和BService相互依赖,但是如果还有一个CService依赖AService或者BService,而AService或者BService也依赖CService呢?而且AService、BService、CService都是单例的,也就是这三个Bean都只能存在一份,只能实例化一次。而三级缓存存放的是实例化后的原始对象封装的对象,如果循环依赖的对象开启了aop,那么这个时候如果没有三级缓存,那么是不是每次都要取出三级缓存中的原始对象进行aop,那么这样的话,最后放入一级缓存的对象就不是相同的,会被覆盖,那么前面赋值的对象会被覆盖,这样没有体现了单例的思想了,所以二级缓存是存放早期比较完整的对象,三级是存放刚刚实例化后的对象。最后放入缓存的对象都是从二级缓存获取的,你就把三级和二级比喻成一个刚刚出生的小孩和准备走上社会的成年人,但是他们都是同一个对象,就算做了aop,那么aop中target还是它自己,从出生到结束都是它一个对象,除非在放入单例池的时候被后置处理器改变了,例如狸猫换太子;所以这里要区分开二级和三级缓存,而且三级缓存还有个好处就是上面说的除了CService也和AService或者BServie相互依赖,那么三级缓存在第一次循环依赖就已经存放到三级缓存,后面的循环依赖就可以直接从三级缓存中取,反正就是不管怎么循环依赖,每个Bean只能存在一份,只能创建一次。循环依赖这块要下来好好去琢磨,光看笔记可不行,还得看源码才行;这边画了一个循环依赖的图,是一个循环依赖处理的大概流程:
在这里插入图片描述

循环源码分析

上面的分析了spring在处理循环依赖的原理和流程分析,下面就开始分析spring在源码的层面是如何处理循环依赖的,spring的循环依赖还是在spring的bean的生命周期中的,在之前的笔记中分析了spring的bean的生命周期,但是循环依赖一直没有说,这里就重点分析下循环依赖的,我们先找到创建bean的入口,一点一点的分析,我们还是根据上面的AService和BService的相互依赖来根据源码分析下
在这里插入图片描述
首先如果先创建AService的话,当spring调用getBean的时候进入 doGetBean方法,看下面的doGetBean,一步一步来分析
代码片段1:

protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {

   //对beanName进行处理,看是否是带了&字符和这个name是不是以别名的形式传入的
   String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   //getSingleton是从单例池中获取,是根据Beanname去获取,如果第一次肯定为空
   Object sharedInstance = getSingleton(beanName);
   .......

首先Object sharedInstance = getSingleton(beanName)这行代码,这个时候AService肯定为空
在这里插入图片描述
图(1)

getSingletion方法

/**
 * Return the (raw) singleton object registered under the given name.
 * <p>Checks already instantiated singletons and also allows for an early
 * reference to a currently created singleton (resolving a circular reference).
 * @param beanName the name of the bean to look for
 * @param allowEarlyReference whether early references should be created or not
 * @return the registered singleton object, or {@code null} if none found
 * 从缓存中获取对象
 * beanName:bean的名字
 * allowEarlyReference:是否允许早期的引用,也就是说如果为true,就可以从早期的应用三级缓存获取,简单来理解就是是否允许从三级
 * 缓存中获取对象
 * 下面的逻辑就是先从一级缓存中获取对象,如果为空,则如果当前要获取的Bean是正在被创建的情况下,从二级缓存中获取
 * 如果二级为空,如果允许从三级缓存获取对象,则从三级缓存获取,执行lambad表达式,最后放入
 * 二级缓存,移除一级缓存
 * 下面的这个方法的逻辑和清晰,但是调用它的逻辑的时机不一样,所表达的意思也不一样
 * 1.时机1:
 *  未出现循环依赖:getBean开始入口的时候调用了它,这个时候的意思是直接从一级缓存中获取,如果一级缓存为空,直接返回了,因为如果对象为空
 * 还要判断这个bean是否正在被创建,而这个时候,正在被创建的beanName还没有放入set集合中,所以会直接返回;
 * 出现循环依赖:当出现循环依赖过后,比如创建A对象的时候要B对象,去创建B,创建B的时候又依赖A,又回去创建A,这个时候来调用
 * 这个方法getSingleton就发现A是正在被创建的,所以就能够获取到三级缓存中的A对象,然后放入三级缓存,移除二级缓存
 * 最后返回放入二级缓存中的A对象赋值给B对象中的A属性,然后B对象完成其他的属性注入和初始化工作 ,最后返回给对象给A,
 * A将返回的对象赋值给A对象中的属性B,然后进行其他属性的填充和初始化工作,最后在把自己初始化好的对象放入单例池。
 *
 * 2.时机2:在创建bean的时候调用这个方法是在bean实例化后来调用的,传入的allowEarlyReference=false,意思只从一二级缓存获取,也就是时机1所说“最后在把自己初始化好的对象放入单例池”来调用的
 * 未出现循环依赖:在这种情况下,Bean正常创建,初始化,aop等等,然后再来一级和二级缓存中获取,因为没有循环依赖,所以它获取的对象肯定是为空
 * 的,所以正常返回它初始化完成过后的对象即可。
 *
 * 出现循环依赖:出现循环依赖的时候,比如A和B出现依赖,A已经处理了循环依赖,这个时候A的实例是被B放入了二级缓存中了,这个时候来调用
 * 这个方法目的很简单,就是要从二级缓存中获取之前被放入二级缓存的A对象,如果获取的A对象和初始化完成后的对象一致
 * 表示A对象在放入二级缓存过后,后续的初始化工作没有改变它自己,还是原来的自己,就返回从二级缓存获取的对象,然后放入单例池中
 *
 * 这个循环依赖大概的过程就是这样,要结合上下文来看循环依赖
 *
 */
@Nullable
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;
}
public boolean isSingletonCurrentlyInCreation(String beanName) {
   return this.singletonsCurrentlyInCreation.contains(beanName);
}

首先我们传入的BeanName是AService,看第一个条件singletonObject 从一级缓存中获取对象,这个时候刚开始创建,这个singletonObject 肯定为空,然后第二个条件isSingletonCurrentlyInCreation也肯定为空,第二个条件的意思就是说如果bean如果正在创建的话,放入singletonsCurrentlyInCreation这个set集合,从上面来看,这个set集合还没有加入进去,所以这里判断的是AService现在还没开始创建,所以代码到这里AService还没开始创建,所以这里返回的null;我们再回到上面的doCreateBean方法,
getSingleton返回null过后,进入下面的逻辑,下面的逻辑就是父子工厂取获取、还有scope的判断,是否是web的作用域那些判断,反正我们这里不满足,这写判断在之前的笔记中已经写了,这里就不写了, 最后代码执行到一个关键点地方
代码片段2:

/**
 * 上面一直没有获取到bean对象,并且处理了依赖的ban,下面就开始处理自己了
 *下面的逻辑就是根据作用域去获取创建bean
 * 1.单例bean
 * 2.原型bean
 * 3.web bean(scope=request or session),在spring mvc中使用
 */
// Create bean instance.
if (mbd.isSingleton()) {
   //如果是单例的,下面的代码上面说过了,反正就是创建bean,createBean这个方法最核心,也太复杂,包括后续的执行流程都在里面
   //这个createBean方法后面还有笔记要写很久
   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);
}

这段代码逻辑是doCreateBean的一段逻辑,就是说经过上面的一些逻辑还没找到bean的话,就进入这个逻辑,这个逻辑就是创建bean的逻辑,也就是说我们的AService必定会执行到这里创建bean, sharedInstance = getSingleton(beanName, () -> {…这段代码就是开始创建bean,这段代码是使用的一个lambad表达式,lambad表达式中createBean就是bean的生命周期开始的地方,我们一定知道的是lambad表达式的作用,我们注意看getSingleton这个方法传入的是一个ObjectFactory,ObjectFactory 是一个函数接口@FunctionInterface,所以执行到这里的时候还不是去执行createBean的时候,什么时候执行呢?是在ObjectFactory.getObject的时候就回去执行lambad表达式,也就是执行createBean了。所以我们首先要看getSingleton的逻辑,到这里还在创建AService刚开始的地方,思路要清晰。

getSingleton

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      //再次去一级缓存中获取一次bean
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
         if (this.singletonsCurrentlyInDestruction) {
            throw new BeanCreationNotAllowedException(beanName,
                  "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                  "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
         }
         if (logger.isDebugEnabled()) {
            logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
         }
         //bean为空,那么这个baen要开始创建了,所以这里添加一个set集合,表示我们的bean正在创建
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
            //这里真正的开始去创建bean,也就是执行本方法传入的lambad表达式,简单来说就是执行createBean方法
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         catch (IllegalStateException ex) {
            // Has the singleton object implicitly appeared in the meantime ->
            // if yes, proceed with it since the exception indicates that state.
            singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
               throw ex;
            }
         }
         catch (BeanCreationException ex) {
            if (recordSuppressedExceptions) {
               for (Exception suppressedException : this.suppressedExceptions) {
                  ex.addRelatedCause(suppressedException);
               }
            }
            throw ex;
         }
         finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
            //这个bean创建完成过后,将正在创建的beanName从set集合中移除
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
            //最后将创建完成过后的bean放入一级缓存,也就是单例池
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

这个就是创建bean的一个关键逻辑,上面的代码逻辑的简单来说就是判断AService是否在单例池中存在,如果不存在,这个时候添加beanName到一个set集合中,表示这个bean正在被创建,然后调用getObject方法真正的开始创建bean,也就是执行lambad表达式,最后将执行完成过后的bean放入单例池。所以我们要看看createBean的方法了,
createBean的方法之前的笔记分析过,我这里只看依赖注入和循环依赖的部分,这里就是分析循环依赖,看spring是如何处理循环依赖的,这里执行lambad表达式就是调用createBean,而createBean会去调用doCreateBean,然后我们看doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   // Instantiate the bean.
   /**
    * 下面的代码开始对bean实例化
    */
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }

   if (instanceWrapper == null) {
      //bean的实例化,里面包含了推断构造方法,简单来说就是对bean进行实例化,根据不同的情况进行构造方法的推断
      //找出最适合的一个构造方法进行实例化
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   Object bean = instanceWrapper.getWrappedInstance();
   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 {
            //spring提供的又一个bean后置处理器,就是在bean实例化完成过后,可以进行调用
            //这个后置处理器可以传入指定的BeanDefinition,也就是你可以改变BeanDefinition的属性
            //但是这个时候bean都已经实例化完成了,就算你修改了beanclass也没有用了
            //但是有些属性我们还是可以设置的,比如可以手动设置初始化的方法mbd.setInitMethodName
            //@AutoWired注解的切入点就是在这个后置处理器中找到的并且注入到缓存中
            //@Resource、@PostConstruct、@PreDestory也是在这里处理的
            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.isTraceEnabled()) {
         logger.trace("Eagerly caching bean '" + beanName +
               "' to allow for resolving potential circular references");
      }
      /**
       * 上面对于bean通过构造方法创建了一个bean的原始对象,创建好原始对象过后,这里将原始对象、beanName、BeanDefinition
       * 封装成一个ObjectFactory对象,然后放入到三级缓存中,三级缓存就singltonObjects,也是一个map,
       * 然后这里把创建的原始对象先放入到三级缓存singltonObjects中,而封装的对象是ObjectFactory,代码执行到这里的时候
       * 是没有去调用lambad表达式的,当从三级缓存取出来的时候调用getObject才会去执行那个lambad表达式。spring处理循环依赖的过程如下:
       * 比如bean A中依赖了B,而B中也依赖了A,执行流程如下:
       * 1.实例化A,在实例化前的时候添加一个set集合,表示A正在创建,将创建出来的A的实例(不完整的)放入三级缓存中;
       * 2.开始填充A中依赖的B的属性,这个时候发现B没有在单例池,也就是一级缓存,然后去创建B;
       * 3.创建B的时候发现B也没有,然后就去实例化B,实例化B完成过后,就去填充属性,填充属性发现B也依赖了A;
       * 而A现在属于正在创建中,然后B就判断出来了A 和 B之间出现了循环依赖,这个时候B从三级缓存中取出A,如果A是有AOP的
       * 那么在这个过程中就会去创建一个A的aop代理对象(是在bean的后置处理器中去创建的调用getEarlyBeanReference),然后将创建出来的a的代理对象
       * 放入到二级缓存中;
       * 4.然后返回这个放入二级缓存的bean普通或者代理对象,然后这个时候B已经创建完成了,只是里面的属性A还不是一个完整的对象,依赖的A
       * 的属性可能还没有赋值完成;
       * 5.然后B创建完成过后返回给A,这个时候对A中的属性进行赋值,就完成了A对于B,B对于A的循环依赖的注入
       * 6.这个时候A就开始填充其他的属性,其他的循环依赖属性以及非循环依赖的属性,等A都填充完成了,并且初始化完成过后;
       * 7.A从二级缓存中取出之前存放的代理对象或者普通对象,然后进行对比,如果两个对象相等,那么说明A在实例化的过程中,没有改变
       * 对象的类型,因为aop是在实例化后进行的,所以就有下面的几种情况:
       * 1.如果A开启了AOP代理:
       *  a.如果A出现了循环依赖,那么在填充属性的时候就会去创建A的代理对象(getEarlyBeanReference)放入二级缓存,
       *  那么A在实例化过后发现A已经创建了代理对象,所以就不会去创建aop代理对象,;
       *  b.如果A没有出现循环依赖,那么填充属性的时候放入二级缓存的就是普通的对象,和之前的实例化出来的原始对象一致。
       * 2.如果a没有开启AOP代理,最后放入二级缓存的也是之前的原始对象;
       *
       *
       */
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // Initialize the bean instance.
   Object exposedObject = bean;
   try {

      /**
       * 填充属性,处理@AutoWried,调用bean的实例化后的方法
       */
      populateBean(beanName, mbd, instanceWrapper);
      /**
       * 这个方法是调用bean的初始化方法的
       */
      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);
      }
   }

   if (earlySingletonExposure) {
      /**
       * 如果A出现了循环依赖,在上面的填充属性的时候就会去找B,而找B的过程又会调用到这里:
       * 1.实例化出来B,添加到三级缓存,移除二级缓存;
       * 2.填充B属性的时候发现B依赖了A,这个时候B发现A正在创建,出现了循环依赖;
       * 3.从三级缓存中获取A的原始对象,如果开启了aop,执行aop代理,生成aop代理对象,放入二级缓存,移除三级缓存;
       * 4.然后将这个代理对象赋值给B中的属性A;
       * 5.B执行其他的属性填充和初始化工作,完成过后,B放入一级缓存单例池;
       * 6.B执行完成过后返回给A,A这个时候将B返回的对象赋值给A中的属性B;
       * 7.A执行其他填充属性和初始化工作,然后A从二级缓存中取出对象,
       * 然后判断初始化完成后的对象是否相等,如果相等,则证明在初始化工作中没有改变A对象,然后将二级缓存中取出来的对象赋值给exposedObject
       * 最后放入单例池
       * 这里有点绕:
       * 1.首先要明白的是A对象首先创建对象放入了三级缓存中;
       * 2.然后进行填充属性,发现要B,就去创建B,B在创建的时候,填充属性发现要A,又会调用getBean的方法去找A,发现
       * A正在创建并且在三级缓存中获取原始对象A,就知道出现了循环依赖,然后调用三级缓存中的getObject,执行lambad表达式
       * 放入二级缓存,移除三级缓存,在调用getObject的时候,如果开启了aop,就返回代理对象;
       * 3.然后B在填充其他的属性并完成初始化工作,最后放入一级缓存单例池中;
       * 4.最后返回B,A开始赋值属性,最后完成依赖注入;
       *
       */
      /**
       * getSingleton(beanName, false)这个方法传入的是false,false就表示说代码执行到这里,只从一级二级缓存中取,
       * 不去三级缓存中取,这里分为是否出现了循环依赖:
       *  a.如果出现了循环依赖,那么填充A中属性B时,会去创建B,创建B的时候发现要A,
       *
       */

      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   // Register bean as disposable.
   try {
      /**
       * 注册所有的销毁方法,在spring容器关闭的时候会调用这里找到的销毁方法的缓存列表,然后执行
       */
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

当AService执行到这里的时候流程如下:
1.调用createBeanInstance创建一个原始的AServie对象;
2.applyMergedBeanDefinitionPostProcessors这个执行是得到AService中的注入点;
3.addSingletonFactory将第一步创建出来的原始对象放入了三级缓存;
4.填充属性;
5.初始化;
6.最后从二级缓存获取对象,判断返回那个对象,然后返回对象。

doCreateBean的方法的执行大概的逻辑就上面几步,我们来分析下,当AService创建bean执行到第二步的时候会找到AService中的所有注入点也就包含了BService的注入点,然后执行第4步,第4步发现要注入BService,而这个时候BService是不在容器中的,所以这个时候又去创建BService,所以这里的思路要回到最初的doGetBean的方法了,又进入了上面的“代码片段1”,图(1),最后创建BService的逻辑又到了doCreateBean,和创建AService的一样进行下面的逻辑:
1.调用createBeanInstance创建一个原始的BServie对象;
2.applyMergedBeanDefinitionPostProcessors这个执行是得到AService中的注入点;
3.addSingletonFactory将第一步创建出来的原始对象放入了三级缓存;
4.填充属性;
5.初始化;
6.最后从二级缓存获取对象,判断返回那个对象,然后返回对象。
创建BService的过程中执行到第4步开始填充属性,因为BService中也有一个注入点是AService,所以它又要去容器找AService,这又去调用doGetBean找AServie了,但是这个时候AService还处于创建中,所有创建BService的过程中又会去调用getBean去找Aservice,执行到“代码片段1”的时候,调用getSingleton,这个时候逻辑就不一样了,首先找的AService,然后AService又在创建中,所以会进入第一个if条件,然后在从二级缓存中获取AService,很显然,这个时候二级缓存中也不会有AService,所以这个时候进入下个if判断,获取三级缓存,这个时候三级缓存是由值的,因为 每个bean在创建出原始对象就会放入三级缓存,但是放入三级缓存的对象也是一个ObjectFactory,也就是包含了一个lambad表达式,所以看下面的代码:

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;
}

singletonObject = singletonFactory.getObject()这里就是调用放入三级缓存的lambad表达式,到这里我们还要看下放入三级缓存的代码:

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference(beanName, mbd, bean) 就是一个lambad表达式,当调用getObject的时候会去调用这个lambad表达式,而这个表达式要做的是什么呢

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;
}

这个方法就是一个调用后置处理器的,这个后置处理器的方法是getEarlyBeanReference,这个方法的实现者如下
在这里插入图片描述
只有两个实现类,而其中只有AbstractAutoProxyCreator真正实现了它的逻辑的,所以到这里我们就知道了,这里是做aop代理的,也就是说一般aop是在实例化后调用后置处理器进行的,但是如果在填充属性也就是依赖注入的时候如果出现循环依赖,也就是从三级缓存取出对象,放入二级缓存的时候,这个时候是循环依赖了,那么如果AService启用了aop ,那么这里放入三级缓存的就是一个AService代理对象,待会儿让下面演示下;所以这里从三级缓存取出对象,然后调用getEarlyBeanReference返回一个对象放入二级缓存,所以创建BService的时候依赖了AService,然后去找AServicec,发现AService正在创建,则出现了循环依赖,从三级缓存取出AService,执行lambad表达式调用getEarlyBeanReference(如果有代理,返回一个代理对象),然后将放入二级缓存的对象AService返回给BServie,这个时候BServie就有了AService的注入对象了,然后进行注入,也就是赋值,将得到的AService对象赋值给BService中AService对象。然后BService执行剩下的逻辑,也就是填充其他的顺序和初始化,完成过后BService创建完毕,然后回到getSingleton方法,将创建的BService放入单例池,最后返回BService的对象给AService,这个时候我们的AService还在创建中,还在等BService返回的对象,所以思绪回到AService的创建过程,如果BService创建成功返回给AService,那么AService的执行逻辑在这里:
在这里插入图片描述
AService开始填充其他的属性,填充完成过后,进行初始化,初始化完成过后进入下面的逻辑
代码片段3:

if (earlySingletonExposure) {
      /**
       * 如果A出现了循环依赖,在上面的填充属性的时候就会去找B,而找B的过程又会调用到这里:
       * 1.实例化出来B,添加到三级缓存,移除二级缓存;
       * 2.填充B属性的时候发现B依赖了A,这个时候B发现A正在创建,出现了循环依赖;
       * 3.从三级缓存中获取A的原始对象,如果开启了aop,执行aop代理,生成aop代理对象,放入二级缓存,移除三级缓存;
       * 4.然后将这个代理对象赋值给B中的属性A;
       * 5.B执行其他的属性填充和初始化工作,完成过后,B放入一级缓存单例池;
       * 6.B执行完成过后返回给A,A这个时候将B返回的对象赋值给A中的属性B;
       * 7.A执行其他填充属性和初始化工作,然后A从二级缓存中取出对象,
       * 然后判断初始化完成后的对象是否相等,如果相等,则证明在初始化工作中没有改变A对象,然后将二级缓存中取出来的对象赋值给exposedObject
       * 最后放入单例池
       * 这里有点绕:
       * 1.首先要明白的是A对象首先创建对象放入了三级缓存中;
       * 2.然后进行填充属性,发现要B,就去创建B,B在创建的时候,填充属性发现要A,又会调用getBean的方法去找A,发现
       * A正在创建并且在三级缓存中获取原始对象A,就知道出现了循环依赖,然后调用三级缓存中的getObject,执行lambad表达式
       * 放入二级缓存,移除三级缓存,在调用getObject的时候,如果开启了aop,就返回代理对象;
       * 3.然后B在填充其他的属性并完成初始化工作,最后放入一级缓存单例池中;
       * 4.最后返回B,A开始赋值属性,最后完成依赖注入;
       *
       */
      /**
       * getSingleton(beanName, false)这个方法传入的是false,false就表示说代码执行到这里,只从一级二级缓存中取,
       * 不去三级缓存中取,这里分为是否出现了循环依赖:
       *  a.如果出现了循环依赖,那么填充A中属性B时,会去创建B,创建B的时候发现要A,
       *
       */

      Object earlySingletonReference = getSingleton(beanName, false);
      if (earlySingletonReference != null) {
         if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
         }
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   // Register bean as disposable.
   try {
      /**
       * 注册所有的销毁方法,在spring容器关闭的时候会调用这里找到的销毁方法的缓存列表,然后执行
       */
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}

Object earlySingletonReference = getSingleton(beanName, false);这个代码又是去获取AService对象的逻辑,这里到底是什么意思呢?我们想一下,前面在Aservice属性填充的时候需要BService,然后去创建BService,BService又要AService,所以将AService从三级缓存中取出来,然后放入三级缓存,所以这里调用getSingleton的逻辑分为两步:
1.如果AService没有出现循环依赖,那么这个方法返回的为null;那么也就不走if里面的逻辑了,也就是正常的bean的创建,直接返回;
2.最重要的是出现了循环依赖,虽然代码只有几行,但是要明白spring设计的逻辑,首先要知道几个对象的意思:
exposedObject:初始化后的对象
bean:原始对象,填充属性后的对象
earlySingletonReference :二级缓存取出的对象

如果说在初始化的过程中,AService没有创建代理对象,然后在后置处理器中也没有改变AService,也就是狸猫换太子,那么属性填充后的exposedObject还是它自己,而earlySingletonReference 是从二级缓存中获取的
那么理解上面的三个对象,我们来分析下下面的if判断(肯定是出现了循环依赖才会进入的)
在这里插入图片描述
虽然简单只有几行代码,但是你要知道它为什么要这么做
首先earlySingletonReference 是从二级缓存中获取的,那么出现了循环依赖,会由ASeravice依赖的BService在创建的过程中放入二级缓存,而放入二级缓存的时候是从三级缓存取出来,然后调用lambad表达式返回的对象放入的二级缓存,所以这里如果Aservice启用了aop,那么这里返回的是一个代理对象,也就是说放入二级缓存和获取的对象是一个代理对象,所以上面的判断意思就是说如果exposedObject==bean的时候
那么证明填充属性后的对象还是原来的那个对象,在后置处理器中没有改变AService这对象,那么就使用二级缓存获取的对象;哎,太绕了,有点说不清楚了。

关于上面的逻辑,我这里在整理下,首先明白几个概念:
假如AService出现了循环依赖,并且开启了AOP代理,那么在配置类中增加@EnableAspectJAutoProxy,就会把aop的注册类导入进来

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

   /**
    * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
    * to standard Java interface-based proxies. The default is {@code false}.
    */
   boolean proxyTargetClass() default false;

   /**
    * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
    * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
    * Off by default, i.e. no guarantees that {@code AopContext} access will work.
    * @since 4.3.1
    */
   boolean exposeProxy() default false;

}

所以就有了AOP的后置处理器AbstractAutoProxyCreator。基于这种情况来分析下:

1.当出现了循环依赖,aop代理对象的生成是在属性填充阶段,也就是在放入二级缓存的时候会去执行aop的代理对象的生成;
2.bean在初始化后的后置处理器中会去生成aop代理对象
但是这里有个问题就是如果出现了循环依赖,那么在放入二级缓存的时候是一个aop代理对象,那么在属性填充完成过后,要进行初始化的工作,初始化后的工作有个也要进行aop,但是那个时候如果发现已经存在了代理对象是不会去创建代理对象的,先明白这个概念,然后再回到上面的那个
exposedObject==bean判断,exposedObject是初始化后返回的对象,bean是属性填充后的对象,那么这里判断的如果相等,而AService还是开启了aop的,那么二级缓存中已经存放了代理对象,而exposedObject是在初始化后的后置处理器会创建代理对象,但是因为由于出现了循环依赖,所以在属性填充阶段就已经创建了代理对象,所以在初始化后的后置处理器中是没有去创建代理对象的,因为代理对象在前面阶段已经创建了,所以这里返回的还是原来的那个对象,所以和bean是相等的,但是由于我们的Aservice是要创建代理对象的,那么所以这里的判断就是如果两个相等,则从二级缓存中获取,如果开启了代理,那么二级缓存中必定是代理对象,如果没有开启代理,那么这几个对象都是同一个,无所谓,返回那个都一样。

spring开启代理对象的地方

这里只分析下循环依赖中涉及到的aop代理对象,aop会有笔记去写,如果@EnableAspectJAutoProxy加了这个注解,那么就默认开启了代理,如果切点存在,那么就会进行代理,我这里说下就上面的代码逻辑如果出现了循环依赖,那么会在属性填充的阶段开启代理,也就是放入二级缓存的对象就是代理对象,所以我们看一个代码逻辑
在这里插入图片描述
上面的getObject就是去调用放入三级缓存中的这个方法
在这里插入图片描述
这个方法getEarlyBeanReference就是二级缓存需要调用将返回的结果放入二级缓存中

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;
}

一看就知道是调用后置处理器的,上面已经说而来调用的后置处理器是AbstractAutoProxyCreator, 是一个bean的后置处理器,是实现了SmartInstantiationAwareBeanPostProcessor 的,所以可以调用里面的getEarlyBeanReference方法
在这里插入图片描述
AbstractAutoProxyCreator.getEarlyBeanReference

public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}

这个方法的名字已经告诉我们了就是获取早期的bean的引用实例,所以这个方法调用的时机就是你出现了循环依赖了,提前来调用,将对象和beanName放入了earlyProxyReferences中, 然后下面开始进行aop代理,当然了你的bean要加入了切点,那么这里就是返回的aop代理对象,然后我们再看初始化后的bean后置处理器
在这里插入图片描述

/*
 * 调用顺序是先
 * 1.调用spring内部的aware方法
 * 2.bean的初始化前的后置处理器
 * 3.bean的初始化方法调用
 * 4.bean的初始化后的方法调用
 */
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
   if (System.getSecurityManager() != null) {
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
         invokeAwareMethods(beanName, bean);
         return null;
      }, getAccessControlContext());
   }
   else {
      //调用默认的aware方法
      invokeAwareMethods(beanName, bean);
   }

   Object wrappedBean = bean;
      if (mbd == null || !mbd.isSynthetic()) {
         //初始化前后置处理器
         //这个后置处理器会调用@PostConstrut的初始化方法,是在InitDestroyAnnotationBeanPostProcessor中定义的

         wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      }

      try {
         //调设置的初始用初始化方法,InitializingBean中的初始化方法或者BeanDefinition中化方法
         invokeInitMethods(beanName, wrappedBean, mbd);

      }
      catch (Throwable ex) {
         throw new BeanCreationException(
            (mbd != null ? mbd.getResourceDescription() : null),
            beanName, "Invocation of init method failed", ex);
   }
   if (mbd == null || !mbd.isSynthetic()) {
      //初始化后后置处理器
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
   }

   return wrappedBean;
}

applyBeanPostProcessorsAfterInitialization这个就是调用初始化的后置处理器
我们这里有个后置处理器AbstractAutoProxyCreator,所以定位到AbstractAutoProxyCreator里面的初始化后的后置处理器

@Override
public Object postProcessAfterInitialization(@Nullable 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;
}

看到没有,里面有个集合earlyProxyReferences,表示早期的对象应用集合,是否已经存在了,如果存在了就不会创建,所以代码到这里是不是就都已经明白了。spring在处理循环依赖的过程中的思路,自始至终都是一个概念,一个单例bean只能存在一次,所以用了2个缓存来解决这个循环依赖,网上经常说用了三级缓存来解决的循环依赖,其实不准确,准备的应该说是用了三级缓存和二级缓存来解决的循环依赖,一二三级缓存各司其职,来完成bean的创建;
总结:
spring将bean的原始对象放入三级缓存;
发现循环依赖,从三级缓存取出对象,调用后置处理器,将返回的对象放入二级缓存;
最后从二级缓存取出对象放入单例池
如果出现循环依赖的bean是开启了aop切点的,那么在属性填充阶段就会进行aop,否则是在实例化后进行的aop。

循环依赖的限制条件

循环依赖在属性注入的时候满足:
相互循环依赖的对象不能都是原型的,必须要有个是单例的才行,如果两个bean都是原型的要报错

构造方法注入没有办法循环依赖,满足一个条件即可,就是延迟加载,Lazy和类上的延迟加载是不一样的,构造方法和属性注入上面加上@Lazy,那么spring在注入的时候也要注入,只是生成一个代理对象,这个代理对象不管你是否开启了aop都会生成的,所以满足这个条件接可以,我们看下下面的例子:

@Component
public class AService {

   private BService bService;


   public AService(BService bService){
      this.bService = bService;
   }

   public void test(){
      System.out.println(bService);
   }


}
@Component
public class BService {

   private AService aService;

   public BService(AService aService){
      this.aService = aService;
   }

   public void test(){
      System.out.println(aService);
   }
}

运行报错如下:
在这里插入图片描述
简单来说就是循环依赖在构造方法注入不能处理,我们想下上面的逻辑就是spring处理循环依赖是通过将原始对象放入了三级缓存来处理的,那么得到的信息就是你构造方法一直在注入,怎么注入,三级缓存都没有办法放入,你实例化就无法得到一个对象
但是也能解决,如果你非要在构造放入依赖注入,根据前面我们分析源码得到的一个知识点就是如果你在属性或者构造方法加而来@Lazy,那么表示是延迟加载,那么这个时候注入的时候只会返回一个代理对象,那么这个时候不会真正的去容器找bean,而是创建了一个代理对象,这个代理对象的target指向了bean而已,所以是可以的,我们先看下效果:
在这里插入图片描述
所以是可以的,那么再验证下是不是我上面所说的是一个代理对象呢?我们Debug调试一下
在这里插入图片描述
确实是一个CGLIB的代理对象,所以证明我们的猜想是正确的。
这边再来会议下,前面的注入生成代理的地方在哪里,就是不管是构造方法注入还是属性注入都会去调用一个方法
在这里插入图片描述
而这个方法的后面有个代码片段如下:
在这里插入图片描述
所以,没错的,就是在这里去获取的延迟加载的代理

@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
   /**
    * 一个三元运算符,判断是否加了@Lazy注解,如果加了就直接创建一个代理对象返回,如果没有加@Lazy注解,那么
    * 就返回一个空,前面调用的方法的逻辑继续往下走
    */
   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;
}

其他就不多说了,写这种笔记太费脑细胞了,很多东西你懂,但是写出来就不是那么回事儿,很多东西需要自己去整理思路逻辑,然后慢慢连接起来。

  • 13
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值