Srping循环依赖和三级缓存

  1. 什么是依赖
    有两个类 ClassA、ClassB,如果ClassA中有一个ClassB类型的属性或者ClassA的构造方法有一个参数类型是ClassB,就表示ClassA依赖于ClassB

    /**
     * ClassA类
     */
    public class ClassA {
        private ClassB classB;
        
    	public ClassA(ClassB classB){
            this.classB = classB;
        }
        
    	public ClassB getClassB() {
            return classB;
        }
    
        public void setClassB(ClassB classB) {
            this.classB = classB;
        }
    }
    
    /**
     * ClassB类
     */
    public class ClassB {
    }
    

    从上面的情况来看,依赖又分为构造方法依赖和属性依赖。我们称这种只是单方面依赖的情况为单向依赖吧。在构造方法依赖中创建ClassA对象时ClassB类的对象必须先存在。

  2. 什么是循环依赖
    两种情况:

    1. 属性依赖
      如果此时ClassB类中也有一个ClassA类型的属性,就称为ClassA类和ClassB类循环依赖了。
      /**
       * ClassB类
       */
      public class ClassB {
          private ClassA classA;
      
      	public ClassA getClassA() {
              return classA;
          }
      
          public void setClassA(ClassA classA) {
              this.classA = classA;
          }
      }
      
      此时还是先创建ClassB对象,再创建ClassA对象,没有问题。
      2. 构造方法依赖
      /**
       * ClassB类
       */
      public class ClassB {
          private ClassA classA;
      
          public ClassB(ClassA classA){
              this.classA = classA;
          }
      
          public ClassA getClassA() {
              return classA;
          }
      
          public void setClassA(ClassA classA) {
              this.classA = classA;
          }
      }
      

    上面这种情况,在创建ClassA的对象之前,要先创建ClassB的对象,而要创建ClassB的对象又必须要先创建ClassA的对象。这样就出现了死循环了。
    所以这种在整条依赖链中,全是构造方法依赖产生的循环依赖,没办法解决,我们不考虑这种情况。
    上面是普通对象循环依赖的情况。

  3. 什么是Spring的循环依赖
    我们知道,从Spring容器中Bean的生命周期来看,Bean在实例化后需要对其属性进行填充,才能成为最终能使用的Bean。那么,在填充属性时也会出现依赖其他Bean对象的情况。这时就出现了和普通对象一样的相互依赖的情况。同样的,单向依赖和全依赖链路都是构造方法依赖的情况我们不考虑。原型模式创建的Bean也不考虑,这种Bean没有循环依赖的问题。
    全依赖链路都是构造方法这种情况在spring中也无法解决。
    还是以上面的ClassA、ClassB 两个类为例子:

    先来看属性依赖的情况

    /**
     * ClassA类
     */
    @Component
    public class ClassA {
    
        @Autowired
        private ClassB classB;
    }
    
    /**
     * ClassB类
     */
    @Component
    public class ClassB {
    
        @Autowired
        private ClassA classA;
    }
    
    /**
     * 扫描配置类
     */
    @ComponentScan
    public class Config {
    }
    
    /**
     * 测试类
     */
    
    public class Main {
        public static void main(String[] args) {
            ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
            ClassA classA = context.getBean(ClassA.class);
            ClassB classB = context.getBean(ClassB.class);
    
            System.out.println(classA);
            System.out.println(classB);
    
            System.out.println(classA.getClassB());
            System.out.println(classB.getClassA());
        }
    }
    

    再修改一下ClassA类,让它从构造方法依赖ClassB类。注意在ClassA类上添加了@DependsOn注解,意思是ClassA类的Bean对象要在classB这个Bean存在后才创建。这就类似于我们上面说的普通对象有构造方法依赖时,必须先创建被依赖的这个对象。

    /**
     * ClassA类
     */
    @Component
    @DependsOn("classB")
    public class ClassA {
    
        private ClassB classB;
    
        @Autowired
        public ClassA(ClassB classB){
            this.classB = classB;
        }
    
        public ClassB getClassB(){
            return this.classB;
        }
    }
    

    上面的两种情况都能正常运行

    com.spring.hello.ClassA@2002fc1d
    com.spring.hello.ClassB@69453e37
    com.spring.hello.ClassB@69453e37
    com.spring.hello.ClassA@2002fc1d
    

    思考这样一个问题,ClassA类的Bean在属性注入时需要一个ClassB类的Bean,此时Spring容器就去创建ClassB类的Bean。发现要创建ClassB类的Bean又要先创建ClassA类的Bean。这不就死循环了吗?
    再看看我们上面说的普通对象的循环依赖时,直接在需要的地方使用未设置过属性的对象,然后再来设置自己的属性。

         ClassA classA = new ClassA();  //实例化classA
         ClassB classB = new ClassB();  //实例化classB
         
         classB.setClassA(classA); 
         classA.setClassB(classB); 
    

    我们解释下上面的过程,现在我们要一个ClassA类的对象,于是就去创建classA对象,发现该对象有一个ClassB类型的属性,如果我们要使用一个完整的classA对象,必须得把该属性赋值,不然就是null。然后我们就转去创建ClassB类的对象classB,发现该对象有一个ClassA类型的属性,也是为了得到一个完整的classB对象,我们得给这个属性赋值。在此之前我们已经创建了一个classA对象,虽然还不是完整的(属性是null),直接拿来用吧,把它赋值给classB对象的属性,这是没有问题的。这里基于这样一个事实,classA对象在属性赋值之前和赋值之后是同一个对象。
    那么,现在classB对象已经创建完整了,再接着classA对象的属性赋值,把classB对象赋值过去。此时两个对象都创建完整了。
    当然,上面的例子是因为我们知道这两个对象循环依赖了,所以按照上面的步骤控制了两个对象的创建流程。但是在spring容器创建ClassA类的Bean的时候,发现要注入一个ClassB类型的属性,于是转去创建ClassB类型的Bean,实例化后发现依赖了一个ClassA类型的属性,这时候Spring应该有一种机制能记得之前已经创建过ClassA的Bean了,虽然是不完整的。这种记忆功能在Spring中是通过缓存来实现的,而且是三级缓存。我们接着往下看。

  4. Spring三级缓存
    Spring使用了缓存来实现了记忆功能。先来看看三级缓存长什么样,从上面的 context.getBean(ClassA.class) 这个方法一直跟进去会经历一系列的方法,最终会走到 DefaultSingletonBeanRegistry类的getSingleton()方法中,看一下这个方法

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

    方法中用到了三个缓存,定义在类的开头

    	// 一级缓存
    	/** Cache of singleton objects: bean name to bean instance. */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    	// 三级缓存
    	/** Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    	// 二级缓存
    	/** Cache of early singleton objects: bean name to bean instance. */
    	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    

    为什么要用三级缓存呢?
    按照我们上面的说法,实现一个记忆功能,一级缓存就够了吧。如果只用一级缓存,考虑这样一种情况,我们把属性未赋值的BeanA放入 singletonObjects 中,刚好这时有其他地方来获取这个Bean,那不是拿到了一个属性为null的Bean吗?Spring作为一个优秀的框架肯定是考虑了这一点的,所以一级缓存是存储完整的Bean的。
    那用二级缓存呢?可以实现,把属性未赋值的BeanA放入earlySingletonObjects ,创建BeanB时从二级缓存取,BeanB可以创建完整,然后放入一级缓存中。再回头来给BeanA属性赋值,从一级缓存就能拿到了,BeanA也创建完整了,完美!
    那么第三级的缓存用来干什么呢?既然用到了肯定是有用处的,考虑下这种情况,如果BeanA对象中有AOP实现的注解方法,比如 @Transactional,这时注入到BeanB的对象应该是BeanA对象的代理对象BeanAPxy。这种情况Spring是怎么解决的呢,我看到很多文章上都有提到下面的说法

Spring有两个选择:
 1. 不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
 2. 不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖的情况,Bean就可以按着Spring设计原则的步骤来创建。
	Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

所以,按照上面的描述我们总结三级缓存的使用:
1). singletonObjects : 缓存初始化完成的Bean,即经历过实例化 -> 属性注入->初始化 步骤的bean。
2). earlySingletonObjects : 缓存提前暴露的原始bean对象或者代理对象,属性还未填充。
3). singletonFactories : 缓存对象的工厂,这个工厂可能会返回一个原始的bean,如果对象上使用了AOP切面,则返回这个对象的代理对象。

那么,三个缓存的使用就应该是这样子:
1). Spring容器先创建了BeanA的实例,然后把该对象的ObjectFactory对象放入三级缓存中,这时候发现需要一个BeanB的属性对象,找遍三级缓存都没有,于是转去创建BeanB。
2). BeanB对象实例化后,也会把该对象的ObjectFactory对象放入三级缓存中,BeanB又依赖BeanA。在第三级缓存中找到了BeanA的ObjectFactory,就调用该ObjectFactory的getObject()方法得到原始的BeanA对象(如果BeanA使用了AOP切面,则得到的是BeanA的代理对象),将其称为earlyBeanA。此时BeanA的ObjectFactory从三级缓存移除,并将earlyBeanA放入二级缓存中。BeanB拿到了earlyBeanA,完成了属性注入和后续的初始化过程,成为一个完整的Bean。然后就把这个BeanB放入到一级缓存中,并且从三级和二级缓存中移除。
3). 此时再回头来给BeanA注入属性,发现一级缓存中已经有完整的BeanB对象,于是BeanA的属性也注入成功,然后也访问一级缓存中,并且从三级和二级缓存中移除。

以上,是我对Spring循环依赖和三级缓存的理解,有错误之处,请指正!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值