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
  • 相关阅读:
    POJ 1015 Jury Compromise【DP】
    POJ 1661 Help Jimmy【DP】
    HDU 1074 Doing Homework【状态压缩DP】
    HDU 1024 Max Sum Plus Plus【DP,最大m子段和】
    占坑补题。。最近占的坑有点多。。。
    Codeforces 659F Polycarp and Hay【BFS】
    Codeforces 659E New Reform【DFS】
    Codeforces 659D Bicycle Race【计算几何】
    廖大python实战项目第四天
    廖大python实战项目第三天
  • 原文地址:https://www.cnblogs.com/hfultrastrong/p/15452298.html
Copyright © 2011-2022 走看看