首页 > 编程语言 >博学谷学习记录 自我总结 用心分享 | Spring源码刨析

博学谷学习记录 自我总结 用心分享 | Spring源码刨析

时间:2023-10-13 17:36:54浏览次数:39  
标签:缓存 Spring 刨析 beanName Object bean 源码 singletonObject 创建

  别再盲目的说spring有三级缓存了,两个缓存只是启动时为了解决循环依赖,spring启动后只有一个缓存有用

一、什么是循环依赖

循环依赖指的就是循环引用,就是两个或多个 bean 相互之间的持有对方,比如 CircleA 引用 CircleB , CircleB 引用 CircleC, CircleC 引用 CircleA,则它们最终反映为一个环。

不是指循环调用,循环调用是方法之间的环调用,比如

public class TestA {
   private TestB testB;
   public void a(){
      //b方法中testC.c()——>testA.a();
      testB.b();
   }

   public static void main(String[] args) {
      new TestA().a();
   }
}

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致StackOverflowError,内存溢出错误。

二、Spring 如何解决循环依赖

Spring容器循环依赖包括构造器循环依赖和 setter循环依赖,那 Spring容器如何解决循环呢?(还是用上面的TestA举例)

1.构造器循环依赖

表示通过构造器注入构成的循环依赖,如

此种情况的循环依赖是无法解决的 ,只能抛出 BeanCurrentlylnCreationException异常 。

可以理解为在准备创建 TestA类时(还未创建), 发现构造方法需要 TestB类,那就去创建 TestB, 在创建 TestB类时又发现 需要 TestC类, 则又去创建 TestC, 最终在创建 TestC时发现又需要 TestA,而最开始的TestA并未被创建,无法使用,所以无法解决。

2. setter循环依赖

表示通过 set注入方式构成的循环依赖 。如

 

这种方式的循环依赖能解决是通过 Spring 容器 在创建bean之后还未初始化时 ,就注册一个ObjectFactory类持有该bean(如下面代码所示),从而使其他 bean 在引用到该 bean时,通过其ObjectFactory的getObject方法(这里调用了getEarlyBeanReference方法,可以理解为返回了bean)获取到该bean

 

三、Spring相关源码解析

 

缓存相关类:重点看AbstractBeanFactory父类DefaultSingletonBeanRegistry,涉及的成员变量有

//第一层缓存 k-> beanName  v->bean实例,bean创建并初始化后,会缓存至该map
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

//第二层缓存 k-> beanName  v->ObjectFactory(ObjectFactory其实就是返回bean实例的引用),bean创建后(初始化之前),会缓存至该map,初始化完成后,从该map移除
//循环依赖相关!!!
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

//循环依赖相关!!!
//第三层缓存 k-> beanName  v->bean实例,在获取bean时,如果第一层缓存没有,第二层缓存有,就调用对应ObjectFactory的getObject方法,并将返回的bean缓存至该map,从第二层缓存中移除
//(其实就是怕ObjectFactory.getObject是个耗时操作,为了提高性能,调用一次后将结果缓存),初始化完成后,从该map移除
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

//循环依赖相关!!!“当前创建bean池”,bean创建前,会放到该set中,创建完并初始化后,从set中移除
private final Set<String> singletonsCurrentlyInCreation =
      Collections.newSetFromMap(new ConcurrentHashMap<>(16));

创建bean时会调用如下方法(按调用顺序):

1.1 doGetBean:获取bean

BeanFactory和ApplicationContext的getBean方法(还有注解注入bean)最终都会走到这个方法

protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {

   String beanName = transformedBeanName(name);
   Object bean;

   // Eagerly check singleton cache for manually registered singletons.
   //重点1!!!从缓存中获取bean,getSingleton(beanName, true),源码见下
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      ...
   }

   else {
      ...

      try {
         RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
         ...
         if (mbd.isSingleton()) {
            //重点2,在创建bean的前后做一些处理,源码见下
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  //重点3,真正去创建bean,会调用doGetBean,源码见下
                  return createBean(beanName, mbd, args);
               }
               ...
            });
            ...
         }
    ...
   return (T) bean;
}

1.2 getSingleton:缓存中获取单例bean

//获取bean时会先调用该方法
//allowEarlyReference,是否允许提前曝光,即从earlySingletonObjects查ObjectFactory
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //先看有没有实例缓存
   Object singletonObject = this.singletonObjects.get(beanName);
   //为null且singletonsCurrentlyInCreation包含beanName,说明该bean正在创建中,比如创建A时,发现引用了B,去创建B,B又引用了A,这样再去创建A时,singletonsCurrentlyInCreation包含了A的beanName
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         //比如A引用B,B引用A、C,C也引用A,在创建B时,会调用A的ObjectFactory.getObject()方法获取A,后面创建C时,也需要A,则直接从earlySingletonObjects中get
         singletonObject = this.earlySingletonObjects.get(beanName);
         //如果没有缓存singletonObject且是要提前曝光
         if (singletonObject == null && allowEarlyReference) {
             //获取singletonObject
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               //缓存,这样就不用再调用singletonFactory.getObject()方法
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

逻辑就是先从实例缓存里拿,没有且bean“正在创建中”(创建A后,设置属性时需要B,去创建B,给B设置属性时又需要A,去创建A时,A就是正在创建中)就从earlySingletonObjects缓存里拿,还没有就从singletonFactories里拿到ObjectFactory,若有,就调用其getObject方法返回bean,并缓存到earlySingletonObjects中

1.3 getSingleton:获取单例bean

这个方法只是个过渡方法,在实际获取bean之前把beanName添加到“当前创建bean池”中,获取之后再从“当前创建bean池”中移除beanName并添加到实例缓存中

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
         ...
         //把beanName加到singletonsCurrentlyInCreation,添加失败就抛BeanCurrentlyInCreationException
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         try {
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
         ...
         finally {
            ...
            //把beanName从singletonsCurrentlyInCreation中移除,移除失败就抛IllegalStateException
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
             //把单例缓存到singletonObjects
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}
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);
   }
}

1.4 createBean:创建bean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
      throws BeanCreationException {

   BeanWrapper instanceWrapper = null;
   ...
   //通过构造器方法反射创建对象,并未初始化,如果设置了构造器引用,如<constructor-arg ref="testb"/>,也会去getBean
   instanceWrapper = createBeanInstance(beanName, mbd, args);
   ...


   //单例,且允许循环依赖,且还在创建中,就需要去解决循环依赖,即放入ObjectFactory
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   if (earlySingletonExposure) {
       //重点!!!上面介绍过该方法,添加bean对应的ObjectFactory
       addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   // 设置bean的属性及调用bean的初始化方法
   Object exposedObject = bean;
   try {
       //获取PropertyValues,遍历初始化然后设值
       //会再次调用beanFactory.getBean(refName),比如A中注解引用B,就会调用beanFactory.getBean(BName)获取B实例赋值给A
      populateBean(beanName, mbd, instanceWrapper);
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   ...
   return exposedObject;
}

四、流程图

五、个人理解部分

假如我来实现循环依赖的解决,可能是如下代码:

public class MySpring {
    //同singletonObjects,缓存beanName和实例
   private Map<String,Object> beanInstanceMap=new HashMap<>();
   //beanName 和BeanDefinition的缓存
   private Map<String, Class> beanDefinationMap=new HashMap<>();
   //模仿BeanDefinition中的PropertyValue
   private Map<String, String> beanPropertyMap=new HashMap<>();

   private void init(){
      //注册bean
      beanDefinationMap.put("testA", TestA.class);
      beanDefinationMap.put("testB", TestB.class);
      beanDefinationMap.put("testC", TestC.class);
      //设置bean之间的引用关系
      beanPropertyMap.put("testA","testB");
      beanPropertyMap.put("testB","testC");
      beanPropertyMap.put("testC","testA");
   }

   private Object getBean(String beanName) throws Exception{
       //1.先从缓存中获取,
      Object instance=beanInstanceMap.get(beanName);
      if (instance==null) {
          //2.为null就去反射创建实例
         instance=beanDefinationMap.get(beanName).newInstance();
         //3.先缓存再去填充bean
         beanInstanceMap.put(beanName,instance);
         //4.拿到配置的属性信息,获取对应bean并设置到bean中
         String property=beanPropertyMap.get(beanName);
         if (!StringUtils.isEmpty(property)){
            Object propertyValue=getBean(property);
            setPropertyValue(instance,property,propertyValue);
         }
      }
      return instance;
   }

   private void setPropertyValue(Object instance, String property, Object propertyValue) throws Exception {
      Field field=instance.getClass().getDeclaredField(property);
      field.setAccessible(true);
      field.set(instance, propertyValue);
   }

   public static void main(String[] args) throws Exception {
      MySpring spring=new MySpring();
      spring.init();
      TestA testA= (TestA) spring.getBean("testA");
      //打印true
      System.out.println(testA.getTestB().getTestC().getTestA()==testA);
   }
}

这里我只使用了一个缓存就解决了循环依赖,所以产生了如下疑问

  1. spring为什么额外还使用了earlySingletonObjects 和singletonFactories 两个map,即套了一层ObjectFactory ? 其实就和FactoryBean一样,允许使用者针对这种情况定制化特殊处理,getEarlyBeanReference方法中遍历SmartInstantiationAwareBeanPostProcessor,将处理结果当作bean,导致可能不是原来的引用
  2. 为什么不创建完了bean就放实例缓存里,而要等装配完成? 因为在initializeBean中,有applyBeanPostProcessorsBeforeInitialization 和 applyBeanPostProcessorsAfterInitialization 方法允许返回别的结果,所以等bean完成全部操作再放入缓存,但是如果在装配时(populateBean)别的bean又引用当前bean,此时实例缓存里没有,不可能再去创建新的,那么就使用中间层ObjectFactory ,虽然不是真正的实例,但是其实差不多,其他bean里的引用是getEarlyBeanReference返回的(其实就是bean),在别的bean创建完成后,当前bean走initializeBean后面的方法,其实exposedObject和bean是一个对象的引用,所以其他bean的里的当前bean也就装配完成了

 

 

 

 

 

 

 

标签:缓存,Spring,刨析,beanName,Object,bean,源码,singletonObject,创建
From: https://www.cnblogs.com/LiuLance/p/17759884.html

相关文章

  • Spring Boot + Redis 延时双删功能,实战来了!
    一、业务场景在多线程并发情况下,假设有两个数据库修改请求,为保证数据库与redis的数据一致性,修改请求的实现中需要修改数据库后,级联修改Redis中的数据。请求一:A修改数据库数据B修改Redis数据请求二:C修改数据库数据D修改Redis数据并发情况下就会存在A—>C—>D—>B的情......
  • Spring远程命令执行漏洞(CVE-2022-22965)原理研究
    一、前置知识SpringMVC参数绑定为了方便编程,SpringMVC支持将HTTP请求中的的请求参数或者请求体内容,根据Controller方法的参数,自动完成类型转换和赋值。之后,Controller方法就可以直接使用这些参数,避免了需要编写大量的代码从HttpServletRequest中获取请求数据以及类型转换。这个......
  • 基于知识图谱建模、全文检索的智能知识管理库(源码)
    一、项目介绍一款全源码,可二开,可基于云部署、私有部署的企业级知识库云平台,一款让企业知识变为实打实的数字财富的系统,应用在需要进行文档整理、分类、归集、检索、分析的场景。知识图谱提供了一种从海量文本和图像中抽取结构化知识的手段,让知识获取更便捷、知识整理更简单、知......
  • PHP+MySQL制作简单动态网站(附详细注释+源码)
    项目介绍项目名称:图书管理系统项目实现的内容:1.用户登录、用户注册、登录界面验证码功能。2.退出登录功能、内容查看、内容添加、内容修改。前端页面设计得有点可能不太专业,将就着用。主要专注在功能的实现。具体实现步骤根目录布置: 1.登录界面实现具体步骤参照文章:PH......
  • Java设计模式-策略模式-基于Spring实现
    1、策略模式1.1、概述策略模式是一种行为设计模式,它允许在运行时选择算法的行为。它将算法封装在独立的策略类中,使得它们可以相互替换,而不影响客户端代码。这种模式通过将算法的选择从客户端代码中分离出来,提供了更大的灵活性和可维护性。在Java中,策略模式的设计理念可以通过以......
  • 手机直播源码,当前页卡指示器的简单实现方法
    手机直播源码,当前页卡指示器的简单实现方法初始化的时候先初始化与list集合size相等的圆点个数:  intsize=mList.size();    initPageIndicator(size);​然后是给ViewPager设置监听事件,并在onPageSelected方法中设置被选中的当前页圆点为选中状态:    mView......
  • SQLite源码编译、修改、调试
    Windows平台VisualStudio2022主要涉及到nmake、makefile文件和windb的使用配置Windows平台下的环境,VisualStudio,C++,文档在SQLite官网文档查看所需参数,然后在makefile中对应查找比如,-DSQLITE_DEBUG选项对应makefile中的DEBUG参数#Setthistooneofthefol......
  • 采用SpringBoot+原生HTML+MySQL开发的电子病历系统源码
    电子病历系统采用“所见即所得、一体化方式”,协助医生和护士准确、标准、快捷实现病历书写、修改、审阅、打印、体温单浏览、医嘱管理等,是提供病历快速简洁化完成的一系列综合型医生病历工作平台。本套电子病历系统主要面向医疗机构医生、护士,提供对住院病人的电子病历书写、保存......
  • springboot2.4下使用JUnit依赖注入失败的解决方案
    首先在pom.xml下引入JUnit必须的包:<dependency><groupId>junit</groupId><artifactId>junit</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId&......
  • SpringBoot 1项目创建及Mybatis-plus实现数据增删改查
    一.项目创建及配置1.项目创建点击finish完成创建pom.xml加上以下依赖<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version>......