热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

SpringBean依赖和三级缓存的案例讲解

这篇文章主要介绍了SpringBean依赖和三级缓存的案例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

spring中的bean依赖有大体上可以分为两类,共3中形式,下面简单介绍一下。

第一类是构造方法中的循环依赖,这种会报错

@Service
public class ServiceA { 
  private ServiceB serviceB; 
  public ServiceA(ServiceB serviceB) {
    this.serviceB = serviceB;
  } 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
public class ServiceB { 
  private ServiceA serviceA; 
  public ServiceB(ServiceA serviceA) {
    this.serviceA = serviceA;
  } 
  public void methodB(){
    System.out.println("b");
  }
}
 
//错误提示
Description: 
The dependencies of some of the beans in the application context form a cycle:
 
┌─────┐
| serviceA defined in file [C:\demo\target\classes\com\example\demo\ServiceA.class]
↑   ↓
| serviceB defined in file [C:\demo\target\classes\com\example\demo\ServiceB.class]
└─────┘

第二类是field循环依赖,它分为两种,第一类循环依赖的作用域scope默认是singleton,启动不会报错

@Service
public class ServiceA { 
  @Autowired
  private ServiceB serviceB;
 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
public class ServiceB { 
  @Autowired
  private ServiceA serviceA;
 
  public void methodB(){
    System.out.println("b");
  }
}

第二种作用域scope为prototype,在这种情况下bean是多例的,按理说这种启动也会报错,但是它成功了。。我也不知道为啥

@Service
@Scope("prototype")
public class ServiceA { 
  @Autowired
  private ServiceB serviceB;
 
  public void methodA(){
    System.out.println("a");
  }
}
 
@Service
@Scope("prototype")
public class ServiceB { 
  @Autowired
  private ServiceA serviceA;
 
  public void methodB(){
    System.out.println("b");
  }
}
 

据我在网上查找的资料,spring可以帮我们处理bean的scope为singleton的field循环依赖,个人感觉应该也是这样,下面说一下它的处理过程。

简单说一下bean的加载过程,当spring启动的时候,首先加载进beanFactory的是beanDefinition,之后会根据beanDefinition判断其是否为sington并且为非抽象类非懒加载,那么之后会去创建bean,

bean的创建分为三步:

1.调用构造方法创建对象实例(这一步完成之后其它对象实例就可以引用它)

2.填充实例内部属性(会依次从三级缓存中获取依赖的bean,如果没有找到,则会先去创建依赖的bean,之后再返回继续填充属性)

3.执行initializeBean方法,进行初始化

当bean进行创建时,会先调用getbean方法->执行doGetBean方法,在doGetBean方法中会调用getSingleton方法,这一步就是从三级缓存中获取对象缓存,因为是刚开始创建bean所以缓存中肯定没有,之后会调用createBean方法,在createBean方法中会调用doCreateBean执行bean的创建过程就是上面的那三步,当bean创建成功之后会将其放入一级缓存之中,此时会将它从三级和二级缓存中删除。

public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
  }
 
  doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
 
    //从缓存中获取实例
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
      //省略代码
    } else {
      //省略代码
      createBean(beanName, mbd, args);
    }
    //将创建完成的bean放入一级缓存
    addSingleton(beanname,object)
  }
 
  protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
    //省略代码
    doCreateBean()
  }
 
  protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
    //根据构造方法创建对象实例
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
 
    //将对象实例放入第三级缓存中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    //实例内部属性填充
    populateBean(beanName, mbd, instanceWrapper);
 
    //初始化bean
    exposedObject = initializeBean(beanName, exposedObject, mbd);
 
    return exposedObject;
  }
 
  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;
  }

其中我们可以看到当第一步执行完毕后会将刚刚创建的实例放入singletonFactories(第三级缓存)中,那么我们下面了解下到底什么是spring的三级缓存。处于最上层的缓存是singletonObjects,它其中存储的对象是完成创建好,可以正常使用的bean,二级缓存叫做earlySingletonObjects,它其中存储的bean是仅执行了第一步通过构造方法实例化,并没有填充属性和初始化,第三级缓存singletonFactories是一个工场。

/** Cache of singleton objects: bean name to bean instance. */
 private final Map singletOnObjects= new ConcurrentHashMap<>(256);
 
 /** Cache of singleton factories: bean name to ObjectFactory. */
 private final Map> singletOnFactories= new HashMap<>(16);
 
 /** Cache of early singleton objects: bean name to bean instance. */
 private final Map earlySingletOnObjects= new HashMap<>(16);

其实在getSingleton方法中会首先从一级缓存中获取bean,一级缓存中没有再从二级缓存中获取,二级也没有就会从三级中获取factory当factory不为null时,则会调用getObject方法获取bean,并将bean放入二级缓存,之后再从三级缓存中删除该key-value对,代码如下:

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<&#63;> singletOnFactory= this.singletonFactories.get(beanName);
   if (singletonFactory != null) {
   singletOnObject= singletonFactory.getObject();
   this.earlySingletonObjects.put(beanName, singletonObject);
   this.singletonFactories.remove(beanName);
   }
  }
  }
 }
 return singletonObject;
 }

那么到此处我们就可以看出为什么不能在构造方法中存在循环依赖了,假如现在有a、b两个service,它们两个互相在构造方法中循环依赖,当项目启动,创建a的bean时执行第一步通过构造方法创建实例,但是发现依赖b的bean,所以就从三级缓存中获取,但是没发现,那么就先挂起a的创建过程,先去创建b,在b创建过程中,又依赖于a,但是三级缓存中也没有a的bean,这样就进入了一个循环创建的过程,自然是不可取的。

而内部field scope为prototype为何也会报错呢,当scope为prototype每次引用它时都会创建一个新的对象,所以也会存在循环创建的过程。

而默认情况下bean的scope为singleton,整个容器中仅有整个service的一个bean,还是假如a、b两service存在field循环依赖,当创建a的bean时,执行完构造方法后,a的实例已生成,将其factory对象存入第三级缓存singletonFactories中,在填充属性时,发现依赖b的bean,但是在缓存中没有b的bean;因此转而去创建b,在此过程中执行完b的构造方法后将其factory也放入三级缓存,此时执行b的属性填充,发现依赖a,从三级缓存中获取a的对象,并将a放入二级缓存中,之后执行intialize初始化,最后将b的bean转入一级缓存;再继续回来创建a,这个时候发现在一级缓存中已经有了b,那么属性填充成功,进行初始化,最后a也放入一级缓存,至此执行完毕。

那么大家可能会感到疑惑,为什么要使用三级缓存呢,感觉没有singletonFactories使用二级缓存也可以呀?

从前面的代码里可以看到向第三级缓存中放置的是一个objectFactory的匿名实现类,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)),当我们从singletonFactories中获取objectFctory然后调用getObject方法获取bean的时候,实际就是通过getEarlyBeanReference获取的object,那么进入这个方法看一下。

它在这里会获取所有的beanPostProcessor实现类,然后从中找出实现了SmartInstantiationAwareBeanPostProcessor的beanPostProcessor,然后调用它的getEarlyBeanReference(obgect,beanName)方法,对bean进行处理,然后进行返回,这些实现类中就有aop的核心AbstractAutoProxyCreator,从这里我们就可以看出来,从第三级缓存objectFactory中获取的obejct是经过了处理的一个代理对象,个人理解三级缓存就是为了获取在创建对象的过程中提前对其进行一些扩展操作。

三级缓存实现bean的扩展,将代理对象放入二级缓存中,供其他依赖该bean的对象的使用,如果没有了三级缓存,将bean扩展放在二级缓存中实现,那么如果有bean a被其他多个bean依赖,那么在其他bean填充属性的过程中会多次获取bean a,这个过程中就会多次执行获取bean a代理,就有些多余,而三级缓存结构就是在第三级缓存完成bean的扩展,生成代理对象,放入二级缓存之中,供其他bean获取。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。


推荐阅读
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • Ralph的Kubernetes进阶之旅:集群架构与对象解析
    本文深入探讨了Kubernetes集群的架构和核心对象,详细介绍了Pod、Service、Volume等基本组件,以及更高层次的抽象如Deployment、StatefulSet等,帮助读者全面理解Kubernetes的工作原理。 ... [详细]
  • Hadoop入门与核心组件详解
    本文详细介绍了Hadoop的基础知识及其核心组件,包括HDFS、MapReduce和YARN。通过本文,读者可以全面了解Hadoop的生态系统及应用场景。 ... [详细]
  • 本文详细探讨了Java中StringBuffer类在不同情况下的扩容规则,包括空参构造、带初始字符串和指定初始容量的构造方法。通过实例代码和理论分析,帮助读者更好地理解StringBuffer的内部工作原理。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
  • 深入了解 Windows 窗体中的 SplitContainer 控件
    SplitContainer 控件是 Windows 窗体中的一种复合控件,由两个可调整大小的面板和一个可移动的拆分条组成。本文将详细介绍其功能、属性以及如何通过编程方式创建复杂的用户界面。 ... [详细]
  • 实体映射最强工具类:MapStruct真香 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 本文探讨了在Linux系统上使用Docker时,通过volume将主机上的HTML5文件挂载到容器内部指定目录时遇到的403错误,并提供了解决方案和详细的操作步骤。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 作为一名专业的Web前端工程师,掌握HTML和CSS的命名规范是至关重要的。良好的命名习惯不仅有助于提高代码的可读性和维护性,还能促进团队协作。本文将详细介绍Web前端开发中常用的HTML和CSS命名规范,并提供实用的建议。 ... [详细]
  • 本文探讨了在 ASP.NET MVC 5 中实现松耦合组件的方法。通过分离关注点,应用程序的各个组件可以更加独立且易于维护和测试。文中详细介绍了依赖项注入(DI)及其在实现松耦合中的作用。 ... [详细]
  • Startup 类配置服务和应用的请求管道。Startup类ASP.NETCore应用使用 Startup 类,按照约定命名为 Startup。 Startup 类:可选择性地包括 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
author-avatar
xialaqimixyBo2_1940_321
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有