【追根究底】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
原文地址:https://www.cnblogs.com/hfultrastrong/p/15452298.html