zoukankan      html  css  js  c++  java
  • Spring 源码学习2

    本文作者:geek,一个聪明好学的同事

    1. 简介

    ​ 我们在日常开发中有时因为业务的需要,需要的bean A中依赖bean B,同时又要在bean B中依赖bean A,如下面代码所示,在实例化A过程中,因为需要依赖B的注入,这时候会触发B的实例化,但是B的实例化中依赖A,这样A与B之间就会形成了一个闭环,也就是所谓的循环依赖。

    @Component
    public class A {
    	@Autowired
    	private B b;
    }
    
    @Component
    public class B {
    	@Autowired
    	private A a;
    }
    
    

    2,spring解决循环依赖的分析

    2.1 关于spring bean缓存的构成

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    	/** Cache of singleton objects: bean name to bean instance. */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    	/** Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name to bean instance. */
    	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
      //............
    }
    
    
    缓存名称 备注
    singletonObjects spring获取bean的第一级缓存,用于存放完全初始化好的bean(所有属性依赖已经注入)
    earlySingletonObjects 第二级缓存,存放提前曝光的单例对象,存放原始的 bean 对象(所有属性依赖尚未注入),用于解决循环依赖。
    singletonFactories 第三级缓存,单例对象构建出来首次存放的工厂类,用于解决循环依赖

    2.2,流程+源码分析

    ​ 以开头的bean A和bean B循环依赖为例,讲述bean A实例化过程中如何解决循环依赖。

    1,从AbstractBeanFactory.doGetBean("a")入口,先从一级缓存singleTonObjects中去获取名为a的bean。

    2,因为a是首次创建,在1中获取不到,且对象a是正在创建中,从二级缓存earlySingletonObjects中获取。

    3,在2过程中当然也获取不到,便从三级缓存中singletonFactories获取。

    4,在过程3中也没有获取到a,便开始了doCreateBean创建。

    5,在doCreateBean中调用createBeanInstance方法返回了一个属性b为null的的bean a后(此时依赖的b没有被注入),便把bean a加入到三级缓存singletonFactories中。

    下面源码来自AbstractAutowireCapableBeanFactory#docreateBean

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    			throws BeanCreationException {
    		//........省略创建bean调用方法.............
    		// Eagerly cache singletons to be able to resolve circular references
    		// even when triggered by lifecycle interfaces like BeanFactoryAware.
    		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    				isSingletonCurrentlyInCreation(beanName));
    		if (earlySingletonExposure) {
    			if (logger.isTraceEnabled()) {
    				logger.trace("Eagerly caching bean '" + beanName +
    						"' to allow for resolving potential circular references");
    			}
    			/**
    			 * 提前把bean放到factory中,以解决循环依赖
    			 */
    			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
          //............................
    		}
    
    
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    		Assert.notNull(singletonFactory, "Singleton factory must not be null");
    		synchronized (this.singletonObjects) {
    			if (!this.singletonObjects.containsKey(beanName)) {
    				this.singletonFactories.put(beanName, singletonFactory);
    				this.earlySingletonObjects.remove(beanName);
    				this.registeredSingletons.add(beanName);
    			}
    		}
    	}
    

    6,调用populateBean对bean a的b属性进行填充,取容器中查找bean b,此时触发bean b的实例化过程。

    7,bean b的实例化过程经历上和bean a相同的1到5步骤,从此时bean b需要populateBean,开始针从容器中获取bean a,此时bean a已经在singletonFactories中,不需要触发bean a的实例化,并且把bean a从singletonFactories移动到earlySingletonObjects,并且把bean b的属性a注入完整。

    @Nullable
    	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    		Object singletonObject = this.singletonObjects.get(beanName);
    		//isSingletonCurrentlyInCreation 判断对象是否在创建过程中
    		//对象有循环依赖,对象就有一个中间状态
    		/**
    		 * 缓存singletonObjects中的bean为null且正在创建
    		 */
    		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) {
    						/**
    						 * 把bean从三级缓存移动到二级缓存
    						 */
    						singletonObject = singletonFactory.getObject();
    						this.earlySingletonObjects.put(beanName, singletonObject);
    						this.singletonFactories.remove(beanName);
    					}
    				}
    			}
    		}
    		return singletonObject;
    	}
    

    8,bean b成功实例化后返回,注意此时的bean b中是持有一个不完整的bean a(属性为null)。bean a找获取到返回的bean b后,最终也初始化成功,bean a此时也被从二级缓存移动回到一级缓存。

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

    3,循环依赖不能被解决情况

    1,双方bean的通过构造函数注入

    @Component
    public class A {
    	private B b;
    	@Autowired
    	public A(B b) {
    		this.b = b;
    	}
    }
    
    @Component
    public class B {
    	private A a;
    	@Autowired
    	public B(A a) {
    		this.a = a;
    	}
    }
    

    ​ 通过构造函数注入的bean,没有依赖的bean作为参数实例化不出来。对于这种情况的处理可以通过给其中一个比如bean A构造函数上加上@Lazy,这样spring在初始化过程中会通过cglib生成一个代理对象b注入到A中。

    2,双方是prototype的bean

    ​ 从AbstractBeanFactory#doGetBean中可以看到,这种prototype的bean存在循环依赖则会抛异常,因为prototype的bean没有暴露在spring的缓存中,每次需要都是重新创建。

    	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
         //.....
          /**
    			 * 原型模式下的bean存在循环依赖则会抛异常
    			 */
    			if (isPrototypeCurrentlyInCreation(beanName)) {
    				throw new BeanCurrentlyInCreationException(beanName);
    			}
        //......
      }
    

    3,双方bean加上@DependsOn

    @DependsOn("b")
    @Component
    public class A {}
    
    @DependsOn("a")
    @Component
    public class B {}
    

    ​ 如A DependsOn B,在bean a实例化之前,必须存在完整的bean b,并不能通过bean的中间态来解决。

    4,总结

    ​ 解决循环依赖归根结底就是就是创造了bean的一个中间状态(只实例化,未初始化),通过fied注入,或者setter注入的bean都可以空构造函数先把bean实例化出来,提前获得对象的引用,对象的属性是延后设置(引用传递的好处),配合三级缓存实现。

    参考

    查看更多文章关注公众号:好奇心森林
    Wechat

  • 相关阅读:
    1.1.28 文字所在段落增加下划线
    Microsoft Project 2010基础使用方法
    16.3 将Win7文档的内容到复制Linux的vi中
    3.4 在Word中的公式和序号之间填充连续的点
    18.25 JLink调试程序步骤
    18.24 Ubuntu修改静态IP
    18.23 inline函数功能
    18.22 sprintf函数功能
    18.21 关键字extern
    18.20 频率单位转换
  • 原文地址:https://www.cnblogs.com/hackingForest/p/13060772.html
Copyright © 2011-2022 走看看