bean的作用域_从spring循环依赖聊起spring bean加载机制和作用域

  1. 循环依赖

循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图:

bf3285299361f65d15fa50e8b40c7a38.png

注意:循环依赖也可以是自己依赖自己,如A依赖A自己。 我们来写个简单的示例:
public class Main {    public static void main(String[] args) throws Exception {        System.out.println(new A());    }}class A {    public A() {        new B();    }}class B {    public B() {        new A();    }}
不出意外的,我们出现了StackOverflowError 栈溢出错误。通常来说是递归或者死循环导致。

8dd02c8ea66590658085943f4d2defea.png

  1. spring是如何解决循环依赖问题的?

关于这个问题的,因为不是本次分享的重点,小编就只做简单介绍了,网上相关的文章一搜一箩筐。 spring实际上是巧妙的使用三级缓存来解决循环依赖问题的,这三级缓存分别为:
  • singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用

  • earlySingletonObjects:提前曝光的单例对象的cache,存放尚未填充属性的原始bean对象

  • singletonFactories:单例对象工厂的cache,存放 bean 工厂对象

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {  ...  // 从上至下 分表代表三级缓存  private final Map singletonObjects = new ConcurrentHashMap<>(256);  private final Map earlySingletonObjects = new HashMap<>(16);  private final Map> singletonFactories = new HashMap<>(16);  ...  /** Names of beans that are currently in creation. */  // 这个缓存也十分重要:它表示bean创建过程中都会在里面待着,它在Bean开始创建时放值,创建完成时会将其移出~  private final Set singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));  /** Names of beans that have already been created at least once. */  // 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合不会重复,至少被创建了一次的,都会放进这里  private final Set alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));}
获取单例Bean的源码如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {  ...  @Override  @Nullable  public Object getSingleton(String beanName) {    return getSingleton(beanName, true);  }  @Nullable  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;  }  ...  public boolean isSingletonCurrentlyInCreation(String beanName) {    return this.singletonsCurrentlyInCreation.contains(beanName);  }  protected boolean isActuallyInCreation(String beanName) {    return isSingletonCurrentlyInCreation(beanName);  }  ...}

以上代码的大致意思是:

先从一级缓存singletonObjects中去获取。获取到就直接return,如果获取不到或者对象正在创建中isSingletonCurrentlyInCreation(),那就再从二级缓存earlySingletonObjects中获取。如果获取到就直接return,如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取,就从三级缓存singletonFactory.getObject()获取。如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动到了二级缓存中。
  1. 使用spring是否就能高枕无忧了?

其实并不能,spring仅能解决单例模式下field属性注入(即setter方法注入)。我们来看以下三种循环依赖情况:

第一种情况是field属性注入单例模型。以下代码示例是可以work的:
@Servicepublic class A {      @Autowired    private B b;}@Servicepublic class B {      @Autowired    private A a;}
第二种情况是使用构造器注入,以下示例代码会报错:
@Servicepublic class A {    public A(B b) {    }}@Servicepublic class B {    public B(A a) {    }}
报错信息如下:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
为什么呢?Spring解决循环依赖依靠的是Bean的中间态,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器是完成初始化(填充了属性),所以构造器的循环依赖无法解决。 第三种情况是用field属性注入prototype模型。如下代码在未使用到时,启动时没问题,但一旦有使用到就会报错:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Servicepublic class A {      @Autowired    private B b;      public void test (){        System.out.println("I am A");    }}@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Servicepublic class B {      @Autowired    private A a;      public void test (){        System.out.println("I am B");    }}@RestController@RequestMapping("/test")public class RecycleTestController {    @Autowired    private A a;    @GetMapping("/test")    public void test(){        a.test();        a.toString();    }}

a714845afbd5188809b023632fc747cc.png

这里可能跟很多小伙伴的认知不一样了,很多小伙伴的认知是使用prototype作用域就不会再有循环依赖问题, 但为什么都prototype模型了还会报错呢?
  1. 你真的会用prototype作用域吗?

首先,spring默认bean的作用域是singleton,如果我们想使用prototype作用域,可以通过@Scope进行修改,来看下面的代码:
 @Service public class SingletonBean{ @Autowired  private PrototypeBean prototypeBean;   public void doSomething(){         System.out.println(prototypeBean.toString());              }}@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)     public class PrototypeBean{}
运行以上代码,你会发现每次输出的内存地址却是相同的, prototypeBean还是单例的,这是怎么回事?

回想下本文章节2中的内容,我们简单分析一下:因为SingletonBean是单例的,所以在项目启动时就会初始化,prototypeBean本质上只是它的一个Property,那么ApplicationContex中只存在一个SingletonBean和一个初始化SingletonBean时创建的一个prototype类型的PrototypeBean。每次调用SingletonBean.doSomething()时,Spring会从ApplicationContex中获取SingletonBean,每次获取的SingletonBean是同一个,所以即便PrototypeBean是prototype的,但PrototypeBean仍然是同一个。每次打印出来的内存地址肯定是同一个。

明白了以上道理,这个问题的解决办法也就比较简单了。事实上,这种prototype作用域时我们不能简单的通过注入的方式注入一个prototypeBean,可以手动调用applicationContext.getBean("prototypeBean")方法每次获取的都是新的实例了。另外,还有个更优雅的写法,那就是我们注入的时候不注入真实实例,而是注入其代理对象,那么每次代理对象获取真实对象时,代理对象会自动帮我们new新的PrototypeBean实例。只需要在@Scope属性中增加一个proxyMode=ScopedProxyMode.TARGET_CLASS属性即可:

@Service@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode=ScopedProxyMode.TARGET_CLASS)     public class PrototypeBean{}
That's all!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值