ITPub博客

首页 > 数据库 > 数据库开发技术 > 一个好汉三个帮—SpringBean的三级缓存

一个好汉三个帮—SpringBean的三级缓存

原创 数据库开发技术 作者:Finally_m 时间:2023-05-18 08:00:00 0 删除 编辑

戳下方卡片,回复暗号【Java面试题】即可获取大厂面试题



 循环依赖引发的惨案


1.通过构造方法实现依赖注入

@Component
public class Cat {
    private Dog dog;
    // 通过构造方法进行注入
    public Cat( Dog dog) {
        this.dog = dog;
    }

    public void drink() {
        System.out.println(dog);
    }
}
@Component
public class Dog {
    private Cat cat;

    public Dog(Cat cat) {
        this.cat = cat;
    }

    public void run() {
        System.out.println(cat);
    }
}


2.运行程序

直接异常,异常提示如下图所示:


关键提示:Is there an unresolvable circular reference?

该异常表示发生了循环引用。


3.异常原因

程序中,Cat类引用了Dog类对象,Dog类又引用了Cat类对象,对象间循环引用了!!!

But,Spring不是已经帮我们解决循环引用了吗。这怎么还会报错,难道我使用了假的的Spring?



 循环依赖的场景


1.自身引用自身



2.两个对象互相引用



3.多个对象间互相引用形成闭环



 Spring中如何解决循环依赖


1.注解注入不存在循环依赖异常问题

上面的测试中,使用构造法方法注入,存在循环依赖问题。

package com.qianfeng.circle;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class Cat {
    @Resource
    private Dog dog;
    
    public void drink() {
        System.out.println(dog);
    }
}
package com.qianfeng.circle;
import org.springframework.stereotype.Component;

@Component
public class Dog {
    @Resource
    private Cat cat;

    public void run() {
        System.out.println(cat);
    }
}


但是如果使用@Autowired或者@Resource修饰属性,是不会存在循环引用问题的。

spring:
  main:
    allow-circular-references: true


那么针对注解注入,Spring中是如何解决循环依赖问题的呢?答案就是Spring中使用到的三级缓存机制。


Spring Boot2.6.0后,默认不再支持循环依赖,我们可以通过如下配置可以开启:

spring:
  main:
    allow-circular-references: true


2.Spring中的三级缓存

Spring中对三级缓存的对象的定义如下:



singletonObjects:一级缓存,存放完全初始化完成的对象

earlySingletonObjects:二级缓存,存放已创建,但是未填充属性的对象。

singletonFactories:三级缓存,存放提前暴露的对象。


3.源码伺候

(1)第一次加载配置时,通过getSingleton()方法获取cat对象(cat对象依赖dog对象),返回值是null。


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


(2)创建cat对象



通过doCreateBean()方法中的createBeanInstance()方法创建cat的对象。



(3)创建cat对象后,通过addSingletonFactory(),将lambda表达式表示的singletonFactory对象加入Spring的三级缓存。



addSingletonFactory()源码如下所示:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(singletonFactory, "Singleton factory must not be null");
   synchronized (this.singletonObjects) {
      if (!this.singletonObjects.containsKey(beanName)) {
         // 将对应beanName的singletonFactory添加到三级缓存
         this.singletonFactories.put(beanName, singletonFactory);
         // 从二级缓存中删除指定beanName的对象
         this.earlySingletonObjects.remove(beanName);
         this.registeredSingletons.add(beanName);
      }
   }
}


ObjectFactory是一个函数式接口:

@FunctionalInterface
public interface ObjectFactory<T> {

   /**
    * Return an instance (possibly shared or independent)
    * of the object managed by this factory.
    * @return the resulting instance
    * @throws BeansException in case of creation errors
    */
   T getObject() throws BeansException;

}


(4)在populateBean()方法中,进行依赖对象的填充。本例中,填充cat对象依赖的dog对象。



(5)通过field.set()向cat对象注入dog对象,此时,dog对象其实还未创建。



(6)于是,通过getBean()获取dog对象,该方法调用doGetBean(),此时,dog对象并不存在。



(7)调用前面说的doCreateBean()方法创建dog对象。



(8)创建dog对象后,将dog对象对应的singletonFactory对象添加到Spring的三级缓存。



(9)调用populateBean(),填充dog对象中依赖的cat对象。



(10)cat对象在前面的逻辑中已经创建。通过getSingleton()获取cat对象,在getSingleton()方法中,cat对象被添加到Spring二级缓存,同时从三级缓存中删除cat对应的singletonFactory对象。


参考前面的getSingleton()源码。

执行getSingleton()中的singletonObject = singletonFactory.getObject()语句时,就会执行() 

-> getEarlyBeanReference(beanName, mbd, bean)这个lambda表达式中的getEarlyBeanReference()方法。

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


(11)获取cat对象后,通过Field,向dog对象注入cat对象。



(12)注入cat对象后,dog对象初始化完毕,将dog对象添加到Spring的一级缓存。同时从二级和三级缓存中删除dog对应的对象。



addSingleton()源码如下所示:

protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) {
      // 向一级缓存添加对象
      this.singletonObjects.put(beanName, singletonObject);
      // 从三级缓存删除对象
      this.singletonFactories.remove(beanName);
      // 从二级缓存删除对象
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
   }
}


(13)dog对象初始化完成后,将dog对象注入cat对象,cat对象初始化完成。并且,初始化完成的cat对象也会添加到Spring的一级缓存。



4.解决构造方法注入的循环依赖异常

以上源码是对注解注入时,解决循环依赖的分析。

那么,构造方法注入的循环依赖异常如何解决呢?其实很简单,在其中一个类的构造方法中,使用@Lazy修饰构造方法的参数即可。代码如下:

package com.qianfeng.circle;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class Cat {

    private Dog dog;
    // 使用懒加载注解@Lazy修饰构造方法的参数
    public Cat(@Lazy Dog dog) {
        this.dog = dog;
    }

    public void drink() {
        System.out.println(dog);
    }
}
package com.qianfeng.circle;

import org.springframework.stereotype.Component;

@Component
public class Dog {

    private Cat cat;

    public Dog(Cat cat) {
        this.cat = cat;
    }

    public void run() {
        System.out.println(cat);
    }
}


如果你还有不明白的地方,可以在评论区或私信给我。关注Java架构栈,干货天天都不断哦。


近期课程上新:
Java基础 | Javaweb基础 | Java高级框架 | 微服务架构 | TiDB分布式数据库入门最佳实践 | VUE全面剖析及前后端联动实战 | 23种设计模式 | 企业级项目实战 | Java面试宝典

原文作者:Java架构栈;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2021-04-13

  • 博文量
    573
  • 访问量
    243794