1. 程式人生 > 實用技巧 >定製個性化echarts 儀表盤

定製個性化echarts 儀表盤

== 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,如Autowired\Resource\Value或自定義註解 1388

  • 9 |postProcessProperties:InstantiationAwareBeanPostProcessor

  • 10 |dependenciesCheck:AbstractAutowireCapableBeanFactory

  • 11 |applyPropertyValues:AbstractAutowireCapableBeanFactory 載入runtime references

  • 12 ||resolveReference:BeanDefinitionValueResolver ,如果是RuntimeBeanReference 將會觸發reference初始化,即依賴例項被初始化

  • 13 initializeBean:AbstractAutowireCapableBeanFactory

  • 14 |invokeAwareMethods:AbstractAutowireCapableBeanFactory 注入BeanNameAware\BeanClassLoaderAware\BeanFactoryAware

  • 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

這裡貼出第二步的程式碼

	/**
	 * 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雖然解決了迴圈依賴的初始化問題,但並不代表不會產生其他風險,編碼時需額外測試對應的邏輯,總的來說,如果可以避免迴圈依賴還是建議不用這樣做