【追根究底】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
被改变,并且有依赖这个bean
的bean
已经创建完成,就会报错。
- 那么
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是有可能在这里被改变的。
- 那为什么会导致报错?
我们来设想一下,有A
、B
两个类互相循环引用。
创建A的过程是这样的A->B
(创建A
,必须先创建B
)B->A
(创建B
,又必须先创建A
,因为A
的引用已经提前暴露了,假设对象号为@1000
)
此时B
创建完成,B
中的对象A
为@1000
现在A
可以继续初始化了(initializeBean
),很不碰巧的是,A
在这里居然被改变了,变成了一个代理对象,对象号为@1001
然后到了第二个处理earlySingletonExposure
的地方,发现从缓存中拿到的对象和当前对象不相等了(@1000
!=@1001
)
接着就看一下是否有依赖A
的Bean
创建完成了,哎,发现还真的有,那就是B
然后想啊,B
中的A
和现在初始化完的A
它不一样啊,这个和单例的性质冲突了!所以,必定得报错!
- 第一次处理很好理解,解决循环引用问题,需要提前暴露引用,如果不清楚可以看一下【细品springboot源码】彻底弄懂spring bean的创建过程(上)
-
现象复现
代码@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
的第一次处理是提前暴露引用,解决循环引用问题。第二次处理是防止对象被改变,造成的已创建对象中持有的对象和这个对象不一致。
- 因为spring提供了
版权声明:本文为qq_18297675原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 字符编码:从基础到乱码解决
2018-10-24 maven 打jar 被引用后 出现 cannot resolve symbol 错误 生成jar包形式代码文件组织格式 非springboot文件组织格式
2018-10-24 Spring 自动转配类 在类中使用@Bean 注解进行转配但是需要排除该类说明