• Spring 初始化时屏蔽 循环依赖 原理


    == Spring 循环依赖

    目前Spring默认支持循环依赖,如:A依赖B的同时,B也依赖A

    @Service
    public class A {
    
        @Resource
        private B b;
    
        @Resource
        private BizClass bizClass;
    
        public BizClass getBizClass() {
            return bizClass;
        }
    }
    
    @Service
    public class B implements InitializingBean, ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        @Resource
        private A a;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            A a = applicationContext.getBean(A.class);
            BizClass bizClass = a.getBizClass();
            System.out.println();
            //do someBiz
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
    
    }
    

    假设Spring先初始化A,则进入加载A的流程,先来看一下Spring初始化Bean的大体流程,由于Spring初始化过程中有诸如FactoryBean等特殊场景,较为复杂且和本话题不直接相关,下面的步骤仅为A为普通bean且为单例(singleton)的情况

    初始化过程中大体步骤

    版本:spring-context 5.2.2.RELEASE

    首先,不管是基于注解加载还是xml配置,Spring会为每个Bean生成1个BeanDefinition,它将持有bean对应的meta信息、propertyValues(placeholder),如:

    • beanClass: A
    • scope: singleton
    • propertyValues : 通过自定义Namesapce诸如的属性
    • lazyInit
    • externallyManagedConfigMembers:外部依赖,如使用@Resource标注的变量

    根据mdb初始化bean的大体流程

    • 1 doGetBean(beanName) :常用的入口

    • 2 getSingleton(baanName) :AbstractBeanFactory 先get实例,看是否已然创建,已创建就直接返回
      创建前先判断实例是否已经创建,这也是Spring规避重复创建bean的常见方式

    • 3 getSingleton(beanName,singletonFactory):AbstractBeanFactory

    • 4 |beforeSignletonCreate :DefaultSingletonBeanRegistry 表明当前bean正在create流程中 signletonsCurrentlyInCreate.add(beanName)

    • 5 createBean(baanName,mdb,args)
      createBeanInstance with BeanWrapper 创建beanWrapper 生成bean实例对象

    • 6 |addSingletonFactory(baanName,objectFactory) 注册bean对应的singletonFactory,factory实际是1个lamda表达式,getObject时可以拿到bean实例对象

    • 7 populateBean :AbstractAutowireCapableBeanFactory

    • 8 |postProcessAfterInstantiation :InstantiationAwareBeanPostProcessor autowriedAnnotation,如AutowiredResourceValue或自定义注解 1388

    • 9 |postProcessProperties:InstantiationAwareBeanPostProcessor

    • 10 |dependenciesCheck:AbstractAutowireCapableBeanFactory

    • 11 |applyPropertyValues:AbstractAutowireCapableBeanFactory 加载runtime references

    • 12 ||resolveReference:BeanDefinitionValueResolver ,如果是RuntimeBeanReference 将会触发reference初始化,即依赖实例被初始化 ===== 触发依赖bean初始化,类似getBean效果 ======

    • 13 initializeBean:AbstractAutowireCapableBeanFactory

    • 14 |invokeAwareMethods:AbstractAutowireCapableBeanFactory 注入BeanNameAwareBeanClassLoaderAwareBeanFactoryAware

    • 15 |applyBeanPostProcessorsBeforeInitialzation:AbstractAutowireCapableBeanFactory init方法被调用前的处理,甚至可以替换spring new出的object

    • 16 |invokeInitMethods:AbstractAutowireCapableBeanFactory 调用afterPropertiesSet() 和自定义的initMethod

    • 17 |applyBeanPostProcessorsAfterInitialzation: init方法被调用后的处理,甚至可以替换spring new出的object

    • 18 registerDisposableeanIfNecessary

    • 19 afterSingletonCreate:DefaultSingletonBeanRegistry remove signletonsCurrentlyInCreate,表明当前create已结束

    从上面的流程中可以看到,加载bean时可能因为postProcessor或者resolveRuntimeReference导致依赖的另1个bean初始化,如第12步

    因此,循序依赖场景下初始化beanA时,由于beanA依赖了beanB,所以初始化beanA的过程中,beanB也会被初始化,而beanB也会因为依赖了beanA去尝试初始化beanA,如果不加处理就演变成了套娃

    解决问题的关键在第2步,形成的依赖链 A -> B -> A 中,第二次getBean(A)时,会发现A已经在创建过程中了,会将A第一次初始化生成的singletonFactory(第6步创建的factory)拿出来,通过getObject获取到bean实例对象,并且直接返回,这样第二次getBean(A)被快速返回,且1个还没有完成初始化的对象,这也是解决循环依赖的关键:第1次getBean(A) 时创建A的实例,后续再getBean(A)时,直接返回第一次创建的对象实例,避免第二次解析A

    doGetBean入口(AbstractBeanFactory)

    	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
    		final String beanName = transformedBeanName(name);
    		Object bean;
    
    		// Eagerly check singleton cache for manually registered singletons.
    		Object sharedInstance = getSingleton(beanName);               //获取已存在的bean或earlyReference(可能未完成初始化的bean)
    		if (sharedInstance != null && args == null) {                 
    			if (logger.isTraceEnabled()) {
    				if (isSingletonCurrentlyInCreation(beanName)) {
    					logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
    							"' that is not fully initialized yet - a consequence of a circular reference");
    				}
    				else {
    					logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
    				}
    			}
    			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    		}
    
    		else { ... }                                               //如果是第一次doGetBean,则会进入正常create流程
    
    		// Check if required type matches the type of the actual bean instance.
    		if (requiredType != null && !requiredType.isInstance(bean)) {
    			try {
    				T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
    				if (convertedBean == null) {
    					throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    				}
    				return convertedBean;
    			}
    			catch (TypeMismatchException ex) {
    				if (logger.isTraceEnabled()) {
    					logger.trace("Failed to convert bean '" + name + "' to required type '" +
    							ClassUtils.getQualifiedName(requiredType) + "'", ex);
    				}
    				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
    			}
    		}
    		return (T) bean;
    	}
    

    第二步的代码

    	/**
    	 * Return the (raw) singleton object registered under the given name.
    	 * <p>Checks already instantiated singletons and also allows for an early
    	 * reference to a currently created singleton (resolving a circular reference).
    	 * @param beanName the name of the bean to look for
    	 * @param allowEarlyReference whether early references should be created or not
    	 * @return the registered singleton object, or {@code null} if none found
    	 */
    	@Nullable
    	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    		Object singletonObject = this.singletonObjects.get(beanName);
    		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {    //第2次get时发现当前正在创建beanName流程中
    			synchronized (this.singletonObjects) {
    				singletonObject = this.earlySingletonObjects.get(beanName);
    				if (singletonObject == null && allowEarlyReference) {
    					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);  //获取到第1次get时创建的factory
    					if (singletonFactory != null) {
    						singletonObject = singletonFactory.getObject();                    //getObject不会new新对象,而是拿到第1次创建的bean
    						this.earlySingletonObjects.put(beanName, singletonObject);
    						this.singletonFactories.remove(beanName);
    					}
    				}
    			}
    		}
    		return singletonObject;                                                       //第2次get时,返回第1次get时创建的实例,该实例还未初始化
    	}
    

    其他风险

    @Service
    public class B implements InitializingBean, ApplicationContextAware {
    
        private ApplicationContext applicationContext;
    
        @Resource
        private A a;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            A a = applicationContext.getBean(A.class);
            BizClass bizClass = a.getBizClass();   //note1
            System.out.println();
            //do someBiz
        }
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
    
    }
    

    note1注释的部分是存在一些隐藏风险的,在循环依赖中,我们拿到的A可能是未完成初始化的bean,甚至可能其所有成员变量都是默认值的情况,如果立即调用A的方法可能出现不可预料的错误,比如通过这种方式获取到a依赖的bizClass是会失败的

    BizClass bizClass = a.getBizClass();   //note1
    

    a虽然不为null,但是a还没有完成初始化,所以a.getBizClass()将返回null

    总结

    Spring虽然解决了循环依赖的初始化问题,但并不代表不会产生其他风险,编码时需额外测试对应的逻辑,总的来说,如果可以避免循环依赖还是建议不用这样做

  • 相关阅读:
    鸽巢原理(The Pigeonhole Principle)(抽屉原理)
    VS2010显示行号
    HDU 2546 饭卡
    组合数学之排列组合(Permutations and Combinations)(四种情况)
    php中的$_SERVER方法初识
    重新认识hasLayout——IE浏览器css bug的一大罪恶根源 转
    javascript实现简单的链式调用
    Javascript 静态类的实现
    Object类相关的属性,方法和操作符
    <转>前端开发中的MCRV模式
  • 原文地址:https://www.cnblogs.com/windliu/p/14082994.html
Copyright © 2020-2023  润新知