1. 程式人生 > 其它 >【追根究底】doCreateBean中為什麼會對earlySingletonExposure處理兩次

【追根究底】doCreateBean中為什麼會對earlySingletonExposure處理兩次

轉載:https://www.freesion.com/article/9459909831/

  • 問題對應的程式碼片段

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    		throws BeanCreationException {
    
    	……
    	// 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");
    		}
    		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    	}
    
    	// Initialize the bean instance.
    	Object exposedObject = bean;
    	try {
    		populateBean(beanName, mbd, instanceWrapper);
    		exposedObject = initializeBean(beanName, exposedObject, mbd);
    	}
    	catch (Throwable ex) {
    		if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
    			throw (BeanCreationException) ex;
    		}
    		else {
    			throw new BeanCreationException(
    					mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
    		}
    	}
        
        //第二次處理
    	if (earlySingletonExposure) {
    		Object earlySingletonReference = getSingleton(beanName, false);
    		if (earlySingletonReference != null) {
    			if (exposedObject == bean) {
    				exposedObject = earlySingletonReference;
    			}
    			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
    				String[] dependentBeans = getDependentBeans(beanName);
    				Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
    				for (String dependentBean : dependentBeans) {
    					if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
    						actualDependentBeans.add(dependentBean);
    					}
    				}
    				if (!actualDependentBeans.isEmpty()) {
    					throw new BeanCurrentlyInCreationException(beanName,
    							"Bean with name '" + beanName + "' has been injected into other beans [" +
    							StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
    							"] in its raw version as part of a circular reference, but has eventually been " +
    							"wrapped. This means that said other beans do not use the final version of the " +
    							"bean. This is often the result of over-eager type matching - consider using " +
    							"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
    				}
    			}
    		}
    	}
    
    	……
    	return exposedObject;
    }
  • 為什麼doCreateBean中為什麼會對earlySingletonExposure處理兩次?

    • 第一次處理很好理解,解決迴圈引用問題,需要提前暴露引用,如果不清楚可以看一下【細品springboot原始碼】徹底弄懂spring bean的建立過程(上)
      這篇文章。
    • 那第二次是什麼意思呢?有什麼用呢?
      來看一下處理的程式碼,我直接把意思註釋在程式碼裡
       if (earlySingletonExposure) {
          //嘗試從快取中獲取單例,注意後面的引數為false,表示不從第三級快取singletonFactories中獲取,為什麼呢?因為這裡不允許迴圈依賴
      	Object earlySingletonReference = getSingleton(beanName, false);
      	//如果不為null,就會進入if條件中,因為earlySingletonReference不為null,說明存在迴圈引用,
      	//為什麼呢?因為第一個處理的時候,會將引用放到singletonFactories快取中,當迴圈依賴注入的時候,
      	//會通過singletonFactories中拿到提前暴露的引用,然後放到第二級快取earlySingletonObjects中。
      	//所以,在這裡拿到了earlySingletonReference,表明存在迴圈引用。
      	if (earlySingletonReference != null) {
      	    //如果相等,那麼就什麼也不做,將earlySingletonReference返回回去即可
      		if (exposedObject == bean) {
      			exposedObject = earlySingletonReference;
      		}
      		//如果不相等(具體為什麼會不相等,下面會單獨說),並且有其它bean依賴這個bean
      		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
      		    //拿到依賴這個bean的所有bean
      			String[] dependentBeans = getDependentBeans(beanName);
      			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
      			for (String dependentBean : dependentBeans) {
      			    //如果存在已經建立完的bean(已經建立完的bean依賴該bean)
      				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
      					actualDependentBeans.add(dependentBean);
      				}
      			}
      			//如果真的存在,那麼就會報錯,為什麼呢?下面會說 
      			if (!actualDependentBeans.isEmpty()) {
      				throw new BeanCurrentlyInCreationException(beanName,
      						"Bean with name '" + beanName + "' has been injected into other beans [" +
      						StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
      						"] in its raw version as part of a circular reference, but has eventually been " +
      						"wrapped. This means that said other beans do not use the final version of the " +
      						"bean. This is often the result of over-eager type matching - consider using " +
      						"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
      			}
      		}
      	}
      }
    • 好了,分析完這段程式碼,可以總結出兩點
      • exposedObject可能會在initializeBean中被改變
      • 如果exposedObject被改變,並且有依賴這個beanbean已經建立完成,就會報錯。
    • 那麼exposedObject為什麼會在initializeBean中被改變?
      來看一下initializeBean程式碼
      protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
      	……
      	Object wrappedBean = bean;
      	if (mbd == null || !mbd.isSynthetic()) {
      	    //初始化前給BeanPostProcessor改變bean的機會
      		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
      	}
      	……
      	if (mbd == null || !mbd.isSynthetic()) {
      	    //初始化後給BeanPostProcessor改變bean的機會
      		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
      	}
      
      	return wrappedBean;
      }
    • 所以,bean是有可能在這裡被改變的。
    • 那為什麼會導致報錯?
      我們來設想一下,有AB兩個類互相迴圈引用。
      建立A的過程是這樣的
      A->B(建立A,必須先建立B
      B->A(建立B,又必須先建立A,因為A的引用已經提前暴露了,假設物件號為@1000
      此時B建立完成,B中的物件A@1000
      現在A可以繼續初始化了(initializeBean),很不碰巧的是,A在這裡居然被改變了,變成了一個代理物件,物件號為@1001
      然後到了第二個處理earlySingletonExposure的地方,發現從快取中拿到的物件和當前物件不相等了(@1000!=@1001
      接著就看一下是否有依賴ABean建立完成了,哎,發現還真的有,那就是B
      然後想啊,B中的A和現在初始化完的A它不一樣啊,這個和單例的性質衝突了!所以,必定得報錯!
  • 現象復現
    程式碼

    @Component
    public class A {
        @Autowired
        B b;
    }
    @Component
    public class B {
        @Autowired
        A a;
    }
    @Component
    public class C implements BeanPostProcessor {
    
        @Nullable
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if(beanName.equals("a")){
                //返回一個新的代理物件回去 
                return new CGLIBProxy(bean).createProxy();
            }
            return bean;
        }
    
        public class CGLIBProxy implements MethodInterceptor {
    
            private Object obj;
            public CGLIBProxy(Object obj){
                this.obj = obj;
            }
    
            public  Object createProxy(){
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(obj.getClass());
                enhancer.setCallback(new CGLIBProxy(obj));
                return enhancer.create();
            }
    
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("----" + method.getName() + "方法開始----");
                Object res = method.invoke(obj, objects);
                System.out.println("----" + method.getName() + "方法結束----");
                return res;
            }
        }
    }
  • 開始debug,建立bean A的時候,需要建立依賴B,在這裡記住A的物件號@3656

    接著建立B

    然後到第二個處理earlySingletonExposure的地方,發現earlySingletonReference為null,因為B還在singletonFactories中,所以第二級快取是拿不到的。

    B建立完成後,接著繼續初始化A,被BeanPostProcessor攔截,改變了Bean

    到第二個處理earlySingletonExposure的地方,發現bean被改變了
    然後發現,B已經建立完成,B裡面的A也已經注入了

    如果繼續往下走,勢必要出現兩個同一類的bean,不符合單例特性,所以直接報錯

  • 總結

    • 因為spring提供了BeanPostProcessor,所以在bean的整個建立週期,都可能存在被改變的情況,所以需要很多的判斷,這也是為什麼bean的建立原始碼看起來這麼的複雜,因為考慮的東西非常多。
    • doCreateBean中對earlySingletonExposure的第一次處理是提前暴露引用,解決迴圈引用問題。第二次處理是防止物件被改變,造成的已建立物件中持有的物件和這個物件不一致。
版權宣告:本文為qq_18297675原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結和本宣告。 本文連結:https://blog.csdn.net/qq_18297675/article/details/103674833