前言

循环依赖问题,算是一道烂大街的面试题了,解毒之前,我们先来回首两个知识点:

初学 Spring 的时刻,我们就知道 IOC,控制反转码,它将原本在程序中手动建立工具的控制权,交由 Spring 框架来治理,不需要我们手动去种种 new XXX。

只管是 Spring 治理,不也得建立工具吗, Java 工具的建立步骤许多,可以 new XXX、序列化、clone() 等等, 只是 Spring 是通过反射 + 工厂的方式建立工具并放在容器的,建立好的工具我们一样平常还会对工具属性举行赋值,才去使用,可以明白是分了两个步骤。

好了,对这两个步骤有个印象就行,接着我们进入循环依赖,先说下循环依赖的观点

什么是循环依赖

所谓的循环依赖是指,A 依赖 B,B 又依赖 A,它们之间形成了循环依赖。或者是 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖。更或者是自己依赖自己。它们之间的依赖关系如下:


这里以两个类直接相互依赖为例,它们的实现代码可能如下:

public class BeanB {    private BeanA beanA;
    public void setBeanA(BeanA beanA) {
  this.beanA = beanA;
 }
}public class BeanA {    private BeanB beanB;
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
 }
}

设置信息如下(用注解方式注入同理,只是为了利便明白,用了设置文件):

<bean id="beanA" class="priv.starfish.BeanA">
  <property name="beanB" ref="beanB"/></bean><bean id="beanB" class="priv.starfish.BeanB">  <property name="beanA" ref="beanA"/></bean>

Spring 启动后,读取如上的设置文件,会按顺序先实例化 A,然则建立的时刻又发现它依赖了 B,接着就去实例化 B ,同样又发现它依赖了 A ,这尼玛咋整?无限循环呀

Spring “一定”不会让这种事情发生的,如前言我们说的 Spring 实例化工具分两步,第一步会先建立一个原始工具,只是没有设置属性,可以明白为"半成品"—— 官方叫 A 工具的早期引用(EarlyBeanReference),以是当实例化 B 的时刻发现依赖了 A, B 就会把这个“半成品”设置进去先完成实例化,既然 B 完成了实例化,以是 A 就可以获得 B 的引用,也完成实例化了,这实在就是 Spring 解决循环依赖的头脑。

有点懵逼

不明白没关系,先有个也许的印象,然后我们从源码来看下 Spring 详细是怎么解决的。

源码解毒

代码版本:5.0.16.RELEASE

在 Spring IOC 容器读取 Bean 设置建立 Bean 实例之前, 必须对它举行实例化。只有在容器实例化后,才可以从 IOC 容器里获取 Bean 实例并使用,循环依赖问题也就是发生在实例化 Bean 的历程中的,以是我们先回首下获取 Bean 的历程。

获取 Bean 流程

Spring IOC 容器中获取 bean 实例的简化版流程如下(排除了种种包装和检查的历程)


也许的流程顺序(可以连系着源码看下,我就不贴了,贴太多的话,呕~呕呕,想吐):

  1. 流程从 getBean 方式最先,getBean 是个空壳方式,所有逻辑直接到doGetBean 方式中

  2. transformedBeanName 将 name 转换为真正的 beanName(name 可能是 FactoryBean 以 & 字符开头或者有别名的情形,以是需要转化下)

  3. 然后通过 getSingleton(beanName) 方式实验从缓存中查找是不是有该实例 sharedInstance(单例在 Spring 的统一容器只会被建立一次,后续再获取 bean,就直接从缓存获取即可)

  4. 若是有的话,sharedInstance 可能是完全实例化好的 bean,也可能是一个原始的 bean,以是再经 getObjectForBeanInstance 处置即可返回

  5. 固然 sharedInstance 也可能是 null,这时刻就会执行建立 bean 的逻辑,将效果返回

第三步的时刻我们提到了一个缓存的观点,这个就是 Spring 为了解决单例的循环依赖问题而设计的 三级缓存

/** 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);

这三级缓存的作用分别是:

  • singletonObjects:完成初始化的单例工具的 cache,这里的 bean 经历过 实例化->属性填充->初始化 以及种种后置处置(一级缓存)

  • earlySingletonObjects:存放原始的 bean 工具(完成实例化然则尚未填充属性和初始化),仅仅能作为指针提前曝光,被其他 bean 所引用,用于解决循环依赖的 (二级缓存)

  • singletonFactories:在 bean 实例化完之后,属性填充以及初始化之前,若是允许提前曝光,Spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成 beanFactory 并加入到 singletonFactories(三级缓存)

我们首先从缓存中试着获取 bean,就是从这三级缓存中查找

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从 singletonObjects 获取实例,singletonObjects 中的实例都是准备好的 bean 实例,可以直接使用    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation() 判断当前单例bean是否正在建立中    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) {
                    // 三级缓存有的话,就把他移动到二级缓存,.getObject() 后续会讲到                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

若是缓存没有的话,我们就要建立了,接着我们以单例工具为例,再看下建立 bean 的逻辑(大括号示意内部类挪用方式):


  1. 建立 bean 从以下代码最先,一个匿名内部类方式参数(总觉得 Lambda 的方式可读性不如内部类好明白)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);
    }getSingleton() 方式内部主要有两个方式public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // 建立 singletonObject
    singletonObject = singletonFactory.getObject();
    // 将 singletonObject 放入缓存
    addSingleton(beanName, singletonObject);
    }

  2. getObject() 匿名内部类的实现真正挪用的又是 createBean(beanName, mbd, args)

  3. 往里走,主要的实现逻辑在 doCreateBean方式,先通过 createBeanInstance 建立一个原始 bean 工具

  4. 接着 addSingletonFactory 添加 bean 工厂工具到 singletonFactories 缓存(三级缓存)

  5. 通过 populateBean 方式向原始 bean 工具中填充属性,并剖析依赖,假设这时刻建立 A 之后填充属性时发现依赖 B,然后建立依赖工具 B 的时刻又发现依赖 A,照样同样的流程,又去 getBean(A),这个时刻三级缓存已经有了 beanA 的“半成品”,这时就可以把 A 工具的原始引用注入 B 工具(并将其移动到二级缓存)来解决循环依赖问题。这时刻 getObject() 方式就算执行竣事了,返回完全实例化的 bean

  6. 最后挪用 addSingleton 把完全实例化好的 bean 工具放入 singletonObjects 缓存(一级缓存)中,打完收工

Spring 解决循环依赖

建议搭配着“源码”看下边的逻辑图,更好下饭


流程实在上边都已经说过了,连系着上图我们再看下详细细节,用大白话再捋一捋:

  1. Spring 建立 bean 主要分为两个步骤,建立原始 bean 工具,接着去填充工具属性和初始化

  2. 每次建立 bean 之前,我们都市从缓存中查下有没有该 bean,由于是单例,只能有一个

  3. 当我们建立 beanA 的原始工具后,并把它放到三级缓存中,接下来就该填充工具属性了,这时刻发现依赖了 beanB,接着就又去建立 beanB,同样的流程,建立完 beanB 填充属性时又发现它依赖了 beanA,又是同样的流程,差其余是,这时刻可以在三级缓存中查到刚放进去的原始工具 beanA,以是不需要继续建立,用它注入 beanB,完成 beanB 的建立

  4. 既然 beanB 建立好了,以是 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成

    ,

    联博以太坊高度

    www.326681.com采用以太坊区块链高度哈希值作为统计数据,联博以太坊统计数据开源、公平、无任何作弊可能性。联博统计免费提供API接口,支持多语言接入。

    ,

这就是单例模式下 Spring 解决循环依赖的流程了。

然则这个地方,不管是谁看源码都市有个小疑惑,为什么需要三级缓存呢,我赶脚二级他也够了呀

革命尚未乐成,同志仍需起劲

跟源码的时刻,发现在建立 beanB 需要引用 beanA 这个“半成品”的时刻,就会触发"前期引用",即如下代码:

ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {
    // 三级缓存有的话,就把他移动到二级缓存    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

singletonFactory.getObject() 是一个接口方式,这里详细的实现方式在

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;
                // 这么一大段就这句话是焦点,也就是当bean要举行提前曝光时,                // 给一个机遇,通过重写后置处置器的getEarlyBeanReference方式,来自界说操作bean                // 值得注重的是,若是提前曝光了,然则没有被提前引用,则该后置处置器并不生效!!!                // 这也正式三级缓存存在的意义,否则二级缓存就可以解决循环依赖的问题                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

这个方式就是 Spring 为什么使用三级缓存,而不是二级缓存的缘故原由,它的目的是为了后置处置,若是没有 AOP 后置处置,就不会走进 if 语句,直接返回了 exposedObject ,相当于啥都没干,二级缓存就够用了。

以是又得出结论,这个三级缓存应该和 AOP 有关系,继续。

在 Spring 的源码中getEarlyBeanReference 是 SmartInstantiationAwareBeanPostProcessor 接口的默认方式,真正实现这个方式的只有**AbstractAutoProxyCreator** 这个类,用于提前曝光的 AOP 署理。

@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   // 对bean举行提前Spring AOP署理   return wrapIfNecessary(bean, beanName, cacheKey);
}

这么说有点干,来个小 demo 吧,我们都知道 Spring AOP、事务等都是通过署理工具来实现的,而事务的署理工具是由自动署理建立器来自动完成的。也就是说 Spring 最终给我们放进容器内里的是一个署理工具,而非原始工具,假设我们有如下一段营业代码:

@Servicepublic class HelloServiceImpl implements HelloService {
   @Autowired   private HelloService helloService;

   @Override   @Transactional   public Object hello() {
      return "Hello JavaKeeper";
   }
}

此 Service 类使用到了事务,以是最终会天生一个 JDK 动态署理工具 Proxy。恰好它又存在自己引用自己的循环依赖,完善相符我们的场景需求。

我们再自界说一个后置处置,来看下效果:

@Componentpublic class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor {

 @Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
  System.out.println("提前曝光了:"+beanName);
  return bean;
 }
}

可以看到,挪用方式栈中有我们自己实现的 HelloProcessor,说明这个 bean 会通过 AOP 署理处置。


再从源码看下这个自己循环自己的 bean 的建立流程:

protected Object doCreateBean( ... ){
 ...
 
 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 需要提前露出(支持循环依赖),就注册一个ObjectFactory到三级缓存 if (earlySingletonExposure) { 
        // 添加 bean 工厂工具到 singletonFactories 缓存中,并获取原始工具的早期引用  //匿名内部方式 getEarlyBeanReference 就是后置处置器   // SmartInstantiationAwareBeanPostProcessor 的一个方式,  // 它的功效为:保证自己被循环依赖的时刻,纵然被其余Bean @Autowire进去的也是署理工具  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 }

 // 此处注重:若是此处自己被循环依赖了  那它会走上面的getEarlyBeanReference,从而建立一个署理工具从  三级缓存转移到二级缓存里 // 注重此时刻工具还在二级缓存里,并没有在一级缓存。而且此时后续的这两步操作照样用的 exposedObject,它仍旧是原始工具~~~ populateBean(beanName, mbd, instanceWrapper);
 exposedObject = initializeBean(beanName, exposedObject, mbd);

 // 由于事务的AOP自动署理建立器在getEarlyBeanReference 建立署理后,initializeBean 就不会再重复建立了,二选一的)     
 // 以是经由这两大步后,exposedObject 照样原始工具,通过 getEarlyBeanReference 建立的署理工具还在三级缓存呢 
 ...
 
 // 循环依赖校验 if (earlySingletonExposure) {
        // 注重此处第二个参数传的false,示意不去三级缓存里再去挪用一次getObject()方式了~~~,此时署理工具还在二级缓存,以是这里拿出来的就是个 署理工具  // 最后赋值给exposedObject  然后return出去,进而最终被addSingleton()添加进一级缓存内里去    // 这样就保证了我们容器里 最终实际上是署理工具,而非原始工具~~~~~  Object earlySingletonReference = getSingleton(beanName, false);
  if (earlySingletonReference != null) {
   if (exposedObject == bean) { 
    exposedObject = earlySingletonReference;
   }
  }
  ...
 }
 
}

自我解惑:

问:照样不太懂,为什么这么设计呢,纵然有署理,在二级缓存署理也可以吧 | 为什么要使用三级缓存呢?

我们再来看下相关代码,假设我们现在是二级缓存架构,建立 A 的时刻,我们不知道有没有循环依赖,以是放入二级缓存提前露出,接着建立 B,也是放入二级缓存,这时刻发现又循环依赖了 A,就去二级缓存找,是有,然则若是此时另有 AOP 署理呢,我们要的是署理工具可不是原始工具,这怎么办,只能改逻辑,在第一步的时刻,不管3721,所有 Bean 一切去完成 AOP 署理,若是是这样的话,就不需要三级缓存了,然则这样不仅没有必要,而且违反了 Spring 在连系 AOP 跟 Bean 的生命周期的设计。

以是 Spring “画蛇添足”的将实例先封装到 ObjectFactory 中(三级缓存),主要要害点在 getObject() 方式并非直接返回实例,而是对实例又使用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方式对 bean 举行处置,也就是说,当 Spring 中存在该后置处置器,所有的单例 bean 在实例化后都市被举行提前曝光到三级缓存中,然则并不是所有的 bean 都存在循环依赖,也就是三级缓存到二级缓存的步骤不一定都市被执行,有可能曝光后直接建立完成,没被提前引用过,就直接被加入到一级缓存中。因此可以确保只有提前曝光且被引用的 bean 才会举行该后置处置。

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) {
             // 三级缓存获取,key=beanName value=objectFactory,objectFactory中存储     //getObject()方式用于获取提前曝光的实例                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 三级缓存有的话,就把他移动到二级缓存                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}


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");
   }
   // 添加 bean 工厂工具到 singletonFactories 缓存中,并获取原始工具的早期引用   //匿名内部方式 getEarlyBeanReference 就是后置处置器   // SmartInstantiationAwareBeanPostProcessor 的一个方式,   // 它的功效为:保证自己被循环依赖的时刻,纵然被其余Bean @Autowire进去的也是署理工具~~~~  AOP自动署理建立器此方式里会建立的署理工具~~~   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

再问:AOP 署理工具提前放入了三级缓存,没有经由属性填充和初始化,这个署理又是若何保证依赖属性的注入的呢?

这个又涉及到了 Spring 中动态署理的实现,不管是cglib署理照样jdk动态署理天生的署理类,署理时,会将目的工具 target 保存在最后天生的署理 $proxy 中,当挪用 $proxy 方式时会回调 h.invoke,而 h.invoke 又会回调目的工具 target 的原始方式。所有,实在在 AOP 动态署理时,原始 bean 已经被保存在 提前曝光署理中了,之后原始 bean 继续完成属性填充和初始化操作。由于 AOP 署理$proxy中保存着 traget 也就是是 原始bean 的引用,因此后续 原始bean 的完善,也就相当于Spring AOP中的 target 的完善,这样就保证了 AOP 的属性填充与初始化了!

非单例循环依赖

看完了单例模式的循环依赖,我们再看下非单例的情形,假设我们的设置文件是这样的:

<bean id="beanA" class="priv.starfish.BeanA" scope="prototype">
   <property name="beanB" ref="beanB"/></bean><bean id="beanB" class="priv.starfish.BeanB" scope="prototype">   <property name="beanA" ref="beanA"/></bean>

启动 Spring,效果如下:

Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB';

Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA';

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

对于 prototype 作用域的 bean,Spring 容器无法完成依赖注入,由于 Spring 容器不举行缓存 prototype 作用域的 bean ,因此无法提前露出一个建立中的bean 。

缘故原由也挺好明白的,原型模式每次请求都市建立一个实例工具,纵然加了缓存,循环引用太多的话,就对照麻烦了就,以是 Spring 不支持这种方式,直接抛出异常:

if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
}

组织器循环依赖

上文我们讲的是通过 Setter 方式注入的单例 bean 的循环依赖问题,用 Spring 的小伙伴也都知道,依赖注入的方式另有组织器注入、工厂方式注入的方式(很少使用),那若是组织器注入方式也有循环依赖,可以搞不?

我们再改下代码和设置文件

public class BeanA {   private BeanB beanB;
   public BeanA(BeanB beanB) {
      this.beanB = beanB;
   }
}public class BeanB { private BeanA beanA;
 public BeanB(BeanA beanA) {
  this.beanA = beanA;
 }
}
<bean id="beanA" class="priv.starfish.BeanA"><constructor-arg ref="beanB"/></bean><bean id="beanB" class="priv.starfish.BeanB"><constructor-arg ref="beanA"/></bean>

执行效果,又是异常


看看官方给出的说法

Circular dependencies

If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.

For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.

One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.

Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).

也许意思是:

若是您主要使用组织器注入,循环依赖场景是无法解决的。建议你用 setter 注入方式取代组织器注入

实在也不是说只要是组织器注入就会有循环依赖问题,Spring 在建立 Bean 的时刻默认是凭据自然排序来举行建立的,我们暂且把先建立的 bean 叫 主 bean,上文的 A 即主 bean,只要主 bean 注入依赖 bean 的方式是 setter 方式,依赖 bean 的注入方式无所谓,都可以解决,反之亦然

以是上文我们 AB 循环依赖问题,只要 A 的注入方式是 setter ,就不会有循环依赖问题。

面试官问:为什么呢?

Spring 解决循环依赖依赖的是 Bean 的“中心态”这个观点,而这个中心态指的是已经实例化,但还没初始化的状态。实例化的历程又是通过组织器建立的,若是 A 还没建立好出来,怎么可能提前曝光,以是组织器的循环依赖无法解决,我一直以为应该先有鸡才气有蛋。

小总结 | 面试这么答

B 中提前注入了一个没有经由初始化的 A 类型工具不会有问题吗?

虽然在建立 B 时会提前给 B 注入了一个还未初始化的 A 工具,然则在建立 A 的流程中一直使用的是注入到 B 中的 A 工具的引用,之后会凭据这个引用对 A 举行初始化,以是这是没有问题的。

Spring 是若何解决的循环依赖?

Spring 为了解决单例的循环依赖问题,使用了三级缓存。其中一级缓存为单例池(singletonObjects),二级缓存为提前曝光工具(earlySingletonObjects),三级缓存为提前曝光工具工厂(singletonFactories)。

假设A、B循环引用,实例化 A 的时刻就将其放入三级缓存中,接着填充属性的时刻,发现依赖了 B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖 A,这时刻从缓存中查找到早期露出的 A,没有 AOP 署理的话,直接将 A 的原始工具注入 B,完成 B 的初始化后,举行属性填充和初始化,这时刻 B 完成后,就去完成剩下的 A 的步骤,若是有 AOP 署理,就举行 AOP 处置获取署理后的工具 A,注入 B,走剩下的流程。

为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

若是没有 AOP 署理,二级缓存可以解决问题,然则有 AOP 署理的情形下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 署理,这样违反了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处置器来在 Bean 生命周期的最后一步来完成 AOP 署理,而不是在实例化后就立马举行 AOP 署理。

推荐学习:全网最权威的Spring+Spring Boot源码剖析!阿里资深架构师用450分钟让你掌握Spring和Spring Boot。