zoukankan      html  css  js  c++  java
  • 【追根究底】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
  • 相关阅读:
    传参总结
    装mongondb数据库
    es6的几种写法
    在github上搭建自己的主页
    云主机,虚拟主机,VPS
    TDD vs. BDD
    流量统计分析软件比较
    深入浅出Node js 读书笔记 思维导图
    浏览器的工作原理解析
    开源搜索引擎的比较
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/15452298.html
Copyright © 2011-2022 走看看