百味皆苦 java后端开发攻城狮

spring注解驱动开发(循环依赖)

2019-05-17
百味皆苦

aop顺序

  • 常用注解:

    • @Before:前置通知,目标方法之前执行
    • @After:后置通知,目标方法之后执行(始终执行)
    • @AfterReturning:返回后通知:执行方法结束前执行(异常不执行)
    • @AfterThrowing:异常通知,出现异常时执行
    • @Around:环绕通知,环绕目标方法执行
  • aop的全部通知顺序是什么?spring boot或者spring boot2对aop的执行顺序影响是什么?

  • 新建一个切面

  • @Aspect
    @Component
    public class MyAspect
    {
        @Before("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))")
        public void beforeNotify()
        {
            System.out.println("******** @Before我是前置通知MyAspect");
        }
      
      
        @After("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))")
        public void afterNotify()
        {
            System.out.println("******** @After我是后置通知");
        }
      
      
        @AfterReturning("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))")
        public void afterReturningNotify()
        {
            System.out.println("********@AfterReturning我是返回后通知");
        }
      
      
        @AfterThrowing("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))")
        public void afterThrowingNotify()
        {
            System.out.println("********@AfterThrowing我是异常通知");
        }
      
      
        @Around("execution(public int com.zzyy.study.service.impl.CalcServiceImpl.*(..))")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable
        {
            Object retValue = null;
            System.out.println("我是环绕通知之前AAA");
            retValue = proceedingJoinPoint.proceed();
            System.out.println("我是环绕通知之后BBB");
            return retValue;
        }
    }
    
  • spring4+springboot1.5.9

  • @SpringBootTest
    @RunWith(SpringRunner.class)  //1.5.9
    public class T1
    {
    @Autowired
    private CalcService service;
      
      
    System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());
      
    System.out.println();
      
    calcService.div(10,2);
    }
    
  • img

  • spring4默认用的是JDK的动态代理

  • spring5+springboot2.3.3

  • @SpringBootTest  //spring5
    public class T1
    {
    @Autowired
    private CalcService service;
      
    @Test
    public void testAop4(){
        System.out.println("spring版本:"+ SpringVersion.getVersion()+"\t"+"SpringBoot版本:"+ SpringBootVersion.getVersion());
      
    System.out.println();
      
    calcService.div(10,2);
    }
      
    }
    
  • img

  • img

  • spring5默认动态代理用的是cglib,不再是JDK的动态代理,因为JDK必须要实现接口,但有些类它并没有实现接口,所以更加通用的话就是cglib

什么是循环依赖

  • 多个bean之间相互依赖,形成了一个闭环。 比如:A依赖于B、B依赖于c、c依赖于A
  • 通常来说,如果问spring容器内部如何解决循环依赖, 一定是指默认的单例Bean中,属性互相引用的场景
  • img
  • 也就是说,Spring的循环依赖,是Spring容器注入时候出现的问题
  • 两种注入方式对循环依赖的影响 https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans 我们A,B循环依赖问题只要A的注入方式是setter且singleton, 就不会有循环依赖问题

循环依赖报错演示

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

  • 构造器方式注入依赖:构造器注入没有办法解决循环依赖, 你想让构造器注入支持循环依赖,是不存在的

  • @Component
    public class ServiceA {
      
    	private ServiceB serviceB;
      
    	public ServiceA(ServiceB serviceB) {
    		this.serviceB = serviceB;
    	}
    }
      
      
    @Component
    public class ServiceB {
      
    	private ServiceA serviceA;
      
        public ServiceB(ServiceA serviceA) {
    		this.serviceA = serviceA;
    	}
    }
      
    /**
     * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
    *
     * 测试后发现,构造器循环依赖是无法解决的
    */
    public class ClientConstructor {
    	public static void main(String[] args) {
    		new ServiceA(new ServiceB(new ServiceA(new ServiceB()))); ....
        }
    }
    
  • 无限套娃。。。。。。。。。。

  • 以set方式注入依赖

  • @Component
    public class ServiceA {
      
    	private ServiceB serviceB;
      
        public void setServiceB(ServiceB serviceB) {
    		this.serviceB = serviceB;
    		System.out.println("A 里面设置了B");
    	}
    }
      
      
    @Component
    public class ServiceB {
      
    	private ServiceA serviceA;
      
        public void setServiceA(ServiceA serviceA) {
    		this.serviceA = serviceA;
    		System.out.println("B 里面设置了A");
    	}
    }
      
      
    public class ClientSet {
    	public static void main(String[] args) {
      
    	//创建serviceA
    	ServiceA serviceA = new ServiceA();
      
    	//创建serviceB
    	ServiceB serviceB = new ServiceB();
      
    	//将serviceA注入到serviceB中
    	serviceB.setServiceA(serviceA);
      
    	//将serviceB注入到serviceA中
    	serviceA.setServiceB(serviceB);
      
    	}
    }
    
  • 默认的单例(singleton)的场景是支持循环依赖的,不报错

  • 原型(Prototype)的场景是不支持循环依赖的,报错

  • 步骤:

  • applicationContext.xml

  •   
    <Bean id="a" class="com.hhf.study.spring.circulardepend.A" >
            <property name="b" ref="b"/>
    </Bean>
          
      
    <Bean id="b" class="com.hhf.study.spring.circulardepend.B">
            <property name="a" ref="a"/>
    </Bean>
          
      
    
  • 默认单例,修改为原型scope=”prototype” 就导致了循环依赖错误

  • <Bean id="a" class="com.hhf.study.spring.circulardepend.A"  scope="prototype">
            <property name="b" ref="b"/>
    </Bean>
          
      
    <Bean id="b" class="com.hhf.study.spring.circulardepend.B" scope="prototype">
            <property name="a" ref="a"/>
    </Bean>
    
  •   
     //nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
     //Error creating bean with name 'a': 578624778
     //Requested bean is currently in creation: Is there an unresolvable circular reference?
      
    //只有单例的bean会通过三级缓存提前暴露来解决循环依赖的问题,因为单例的时候只有一份,随时复用,那么就放到缓存里面
    //而多例的bean,每次从容器中荻取都是—个新的对象,都会重B新创建, 
    //所以非单例的bean是没有缓存的,不会将其放到三级缓存中。
    public class ClientSpringContainer {
    	public static void main(String[] args) {
                ApplicationContext context = new 	ClassPathXmlApplicationContext("applicationContext.xml");
    	A a = context.getBean("a",A.class);
    	B b = context.getBean("b",B.class);
    	}
    }
    
  • img

  • 重要结论(spring内部通过3级缓存来解决循环依赖)

  • 所谓的三级缓存其实就是spring容器内部用来解决循环依赖问题的三个map 第一级缓存〈也叫单例池)singletonObjects:存放已经经历了完整生命周期的Bean对象 第二级缓存: earlySingletonObjects,存放早期暴露出来的Bean对象,Bean的生命周期未结束(属性还未填充完整) 第三级缓存: Map> singletonFactories,存放可以生成Bean的工厂

  • img

  • img

循环依赖debug

  • 实例化:在堆内存中申请一块内存空间
  • 初始化属性填充:完成属性的各种赋值

三大Map四大方法

  • img

  • 三大缓存

    • 第一层singletonObjects存放的是已经初始化好了的Bean
    • 第二层earlySingletonObjects存放的是实例化了,但是未初始化的Bean
    • 第三层singletonFactories存放的是FactoryBean。假如A类实现了FactoryBean,那么依赖注入的时候不是A类,而是A类产生的Bean
  • 四大方法

    • getSingleton:希望从容器里面获得单例的bean,没有的话就去创建
    • doCreateBean: 没有就创建bean
    • populateBean: 创建完了以后,要填充属性
    • addSingleton: 填充完了以后,再添加到容器进行使用

    • img
  • A/B两对象在三级缓存中的迁移过程

    • A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B
    • B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A
    • B顺利初始化完毕,将自己放到一级缓存里面(此时B里面的A依然是创建中状态)
    • 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。
  • 第一个断点

  • image-20210826215127325

  • 第二个断点

  • image-20210826215342831

  • 第三个断点

  • image-20210826215839743

  • 第四个断点

  • image-20210826220040247

  • 第五个断点

  • image-20210826220341512

  • 第六个断点

  • image-20210826220551362

  • 第七个断点

  • image-20210826221054651

  • 第八个断点

  • image-20210826221321736

  • 第九个断点

  • image-20210826221417738

  • 第十个断点

  • image-20210826221546710

  • 第十一个断点

  • image-20210826221818485

  • 第十二个断点

  • image-20210826222034124

  • 第十三个断点

  • image-20210826222334701

  • 第十四个断点

  • image-20210826222535556

  • 第十五断点

  • image-20210826222657671

  • 第十六断点

  • image-20210826222811677

  • 第十七断点

  • image-20210826222920476

  • 第十八断点

  • image-20210826223800345

  • 第十九断点

  • image-20210826224545620

  • 第二十断点

  • image-20210826224759791

  • 整体流程

  • image-20210826230512259

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

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

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

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

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

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

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

    8 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的beanA

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

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

总结

  • 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,走剩下的流程。


Similar Posts

Comments