Spring AOP与循环依赖

Spring

 

01_spring的aop顺序

AOP常用注解

  • @Before 前置通知:目标方法之前执行

  • @After 后置通知:目标方法之后执行(始终执行)

  • @AfterReturning 返回后通知:执行方法结束前执行(异常不执行)

  • @AfterThrowing 异常通知:出现异常时候执行

  • @Around 环绕通知:环绕目标方法执行

面试题

  • 你肯定知道spring,那说说aop的全部通知顺序springboot或springboot2对aop的执行顺序影响? spring4和spring5的对于aop的执行顺序是不同的。

  • 说说你使用AOP中碰到的坑?

引入AOP



    org.springframework.boot
    spring-boot-starter-aop
@Component
@Aspect
public class MyAspect {
​
    @Before("execution(public int com.atguigu.springdemo.Service.impl.CalServiceImpl.*(..))")
    public void beforeNotify() {
        System.out.println("********@Before我是前置通知");
    }
​
    @After("execution(public int com.atguigu.springdemo.Service.impl.CalServiceImpl.*(..))")
    public void afterNotify() {
        System.out.println("********@After我是后置通知");
    }
​
    @AfterReturning("execution(public int com.atguigu.springdemo.Service.impl.CalServiceImpl.*(..))")
    public void afterReturningNotify() {
        System.out.println("********@AfterReturning我是返回后通知");
    }
​
​
    @AfterThrowing(" execution(public int com.atguigu.springdemo.Service.impl.CalServiceImpl.*(..))")
    public void afterThrowingNotify() {
        System.out.println("********@AfterThrowing我是异常通知");
    }
​
    @Around(" execution(public int com.atguigu.springdemo.Service.impl.CalServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object retvalue = null;
        System.out.println("我是环绕通知之前AAA");
        retvalue = proceedingJoinPoint.proceed();
        System.out.println("我是环绕通知之后BBB");
        return retvalue ;
    }
    
}

小结

Spring4中AOP执行顺序:

  • 正常情况下:@Around环绕通知AAA---->@Before前置通知----->方法()-----> @Around环绕通知BBB----->@After后置通知----->@AfterRunning正常返回

  • 异常情况下:@Around环绕通知AAA---->@Before前置通知----->方法()-----> @After后置通知----->@AfterThrowing方法异常

Spring5中AOP执行顺序:

  • 正常情况下:@Around环绕通知AAA---->@Before前置通知----->方法()----->@AfterRunning正常返回----->@After后置通知 ----->@Around环绕通知BBB

  • 异常情况下:@Around环绕通知AAA---->@Before前置通知----->方法()----->@AfterThrowing方法异常-----> @After后置通知

Spring AOP与循环依赖_第1张图片

02_spring循环依赖

大厂面试题:

  • 你解释下spring中的三级缓存?

  • 三级缓存分别是什么?三个Map有什么异同?

  • 什么是循环依赖?请你谈谈?看过spring源码吗?

  • 如何检测是否存在循环依赖?实际开发中见过循环依赖的异常吗?

  • 多例的情况下,循环依赖问题为什么无法解决?

1_什么是循环依赖?

多个bean之间相互依赖,形成了一个闭环。比如:A依赖于B、B依赖于C、C依赖于A。

通常来说,如果问Spring容器内部如何解决循环依赖,一定是指默认的单例Bean中,属性互相引用的场景。

img

2_两种注入方式对循环依赖的影响

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

循环依赖现象在spring容器中注入依赖的对象,有2种情况

  • 构造器方式注入依赖(不可行):因为A中需要B属性,B中需要A属性,构造器无法满足,会出现编译异常

  • 以set方式注入依赖(可行)

@Component
public class ServiceAA{
​
    private ServiceBB serviceBB;
​
    public void setServiceBB(ServiceBB serviceBB){
        this.serviceBB = serviceBB;
        System.out.println("A里面设置了B");
    }
}
​
public class ClientSet{
    
    public static void main(String[] args){
        
        //创建serviceAA
        ServiceAA a = new ServiceAA();
        //创建serviceBB
        ServiceBB b = new ServiceBB();
        //将serviceA入到serviceB中
        b.setServiceAA(a);
        //将serviceB法入到serviceA中
        a.setServiceBB(b);
        
    }
}
  • 默认的单例(Singleton)的场景是支持循环依赖的,不报错

  • 原型(Prototype)的场景是不支持循环依赖的,会报错:Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

3_spring内部通过3级缓存来解决循环依赖 - DefaultSingletonBeanRegistry

只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题。

非单例的bean,每次从容器中获取都是一个新的对象,都会重新创建,所以非单例的bean是没有缓存的,不会将其放到三级缓存中。

第一级缓存(也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象。---成品

第二级缓存:earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完。)---半成品

第三级缓存:Map> singletonFactories,存放可以生成Bean的工厂。---生产工厂

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    private final Map singletonObjects = new ConcurrentHashMap(256);  // 1级缓存
    private final Map> singletonFactories = new HashMap(16);  // 3级缓存
    private final Map earlySingletonObjects = new ConcurrentHashMap(16);  // 2级缓存

4_前置知识

  • 实例化 - 内存中申请一块内存空间,如同租赁好房子,自己的家当还未搬来。

  • 初始化属性填充 - 完成属性的各种赋值,如同装修,家具,家电进场。

3个Map和四大方法,总体相关对象

img

第一层singletonObjects存放的是已经初始化好了的Bean,

第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean,

第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean

A / B两对象在三级缓存中的迁移说明

  • A创建过程中需要B,于是A将自己放到三级缓里面,去实例化B。

  • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

  • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

03_spring循环依赖debug

1_debug源码01

refresh()方法就是加载容器初始化的方法。

spring底层do开头的方法是用来做什么的?----真正调用业务逻辑的代码,例如,getBean()方法的底层调用的就是doGetBean()

A / B两对象在三级缓存中的迁移说明

  • A创建过程中需要B,于是A将自己放到三级缓里面,去实例化B。

  • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A。

  • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态),然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

2_debug源码02

Spring创建 bean主要分为两个步骤,创建原始bean对象,接着去填充对象属性和初始化

每次创建 bean之前,我们都会从缓存中查下有没有该bean,因为是单例,只能有一个

当我们创建 beanA的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了beanB,接着就又去创建beanB,同样的流程,创建完beanB填充属性时又发现它依赖了beanA又是同样的流程,

不同的是:这时候可以在三级缓存中查到刚放进去的原始对象beanA,所以不需要继续创建,用它注入 beanB,完成 beanB的创建

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

img

Spring解决循环依赖依靠的是Bean的"中间态"这个概念,而这个中间态指的是已经实例化但还没初始化的状态—>半成品。实例化的过程又是通过构造器创建的,如果A还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。

Spring为了解决单例的循坏依赖问题,使用了三级缓存:

其中一级缓存为单例池(singletonObjects)。

二级缓存为提前曝光对象(earlySingletonObjects)。

三级级存为提前曝光对象工厂(singletonFactories) 。

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


Spring解决循环依赖过程:

  • 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA

  • 在getSingleton()方法中,从一级缓存中查找,没有,返回null

  • doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)

  • 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean方法

  • 进入AbstractAutowireCapableBeanFactory#doCreateBean,先反射调用构造器创建出beanA的实例,然后判断:是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中(即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中

  • 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB

  • 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性

  • 此时 beanB依赖于beanA,调用getSingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA,早期引用的A,此时的A并没有初始化,类似于还是毛坯房。

  • 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中,此时的B在一级缓存中

  • 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

3_面试题:我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???

参考网址:https://www.cnblogs.com/semi-sub/p/13548479.html

只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景。

Spring AOP与循环依赖_第2张图片

我们发现AService的testAopProxy被AOP代理了,看看传入的匿名内部类的getEarlyBeanReference返回的是什么对象

Spring AOP与循环依赖_第3张图片

发现singletonFactory.getObject()返回的是一个AService的代理对象,还是被CGLIB代理的。再看一张再执行一遍singletonFactory.getObject()返回的是否是同一个AService的代理对象

Spring AOP与循环依赖_第4张图片

我们会发现再执行一遍singletonFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singletonFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singletonFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。还有一个注意的点

既然singletonFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过CGLIB代理的AService对象。所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象

 

你可能感兴趣的:(java面试题,java)