热门标签 | 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获取。

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


推荐阅读
  • 函子(Functor)是函数式编程中的一个重要概念,它不仅是一个特殊的容器,还提供了一种优雅的方式来处理值和函数。本文将详细介绍函子的基本概念及其在函数式编程中的应用,包括如何通过函子控制副作用、处理异常以及进行异步操作。 ... [详细]
  • Docker安全策略与管理
    本文探讨了Docker的安全挑战、核心安全特性及其管理策略,旨在帮助读者深入理解Docker安全机制,并提供实用的安全管理建议。 ... [详细]
  • 使用Echarts for Weixin 小程序实现中国地图及区域点击事件
    本文介绍了如何使用Echarts for Weixin在微信小程序中构建中国地图,并实现区域点击事件。包括效果展示、条件准备和逻辑实现的具体步骤。 ... [详细]
  • 将字符串中的嵌套列表转换回嵌套列表 ... [详细]
  • 本文将探讨如何在 Struts2 中使用 ActionContext 和 ServletActionContext 来获取请求参数和会话信息,同时解释它们的内部机制和最佳实践。 ... [详细]
  • RTThread线程间通信
    线程中通信在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取& ... [详细]
  • 本文介绍了存储器的基本原理及其分类,包括不同类型的存储介质和存储方式,并详细解释了各种存储器的特点和应用场景。 ... [详细]
  • 本文总结了近年来在实际项目中使用消息中间件的经验和常见问题,旨在为Java初学者和中级开发者提供实用的参考。文章详细介绍了消息中间件在分布式系统中的作用,以及如何通过消息中间件实现高可用性和可扩展性。 ... [详细]
  • DirectShow Filter 开发指南
    本文总结了 DirectShow Filter 的开发经验,重点介绍了 Source Filter、In-Place Transform Filter 和 Render Filter 的实现方法。通过使用 DirectShow 提供的类,可以简化 Filter 的开发过程。 ... [详细]
  • 如何在DedeCMS专题页节点文档中调用自定义模型字段?
    在完成DedeCMS专题页节点文章列表样式的修改后,如果需要在列表中显示自定义模型的字段,由于DedeCMS默认不支持这一功能,因此需要进行一些二次开发。本文将详细介绍如何通过修改模板文件和核心文件来实现这一需求。 ... [详细]
  • 本文详细介绍了CSS中元素的显示模式,包括块元素、行内元素和行内块元素的特性和应用场景。 ... [详细]
  • 本文介绍了如何将Spring属性占位符与Jersey的@Path和@ApplicationPath注解结合使用,以便在资源路径中动态解析属性值。 ... [详细]
  • ABP框架是ASP.NET Boilerplate的简称,它不仅是一个开源且文档丰富的应用程序框架,还提供了一套基于领域驱动设计(DDD)的最佳实践架构模型。本文将详细介绍ABP框架的特点、项目结构及其在Web API优先架构中的应用。 ... [详细]
  • 作为一名新手开发者,我正在尝试使用 ASP.NET 和 Vue.js 构建一个单页面应用,涉及多个复杂组件(如按钮、图表等)。希望有经验的开发者能够提供指导。 ... [详细]
  • 深入理解Java多线程与并发机制
    本文探讨了Java多线程和并发机制的核心概念,包括多线程类的分类、执行器框架、并发容器及控制工具。通过详细解析这些组件,帮助开发者更好地理解和应用多线程技术。 ... [详细]
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社区 版权所有