关于FactoryBeanRegistrySupport的getObjectFromFactoryBean

来源:8-2 夺取doGetBean之从缓存获取Bean

慕设计0394643

2021-09-06

老师说,可能有异步的方式,所以双重检查里面,再一次的尝试从缓存中获取bean。我有个疑问就是,老师说的好像有点问题,如果是异步的话,虽然没有创建成功,但是执行object = doGetObjectFromFactoryBean(factory, beanName);后,object是不为空的,在线程A释放锁前肯定会执行下面

						if (containsSingleton(beanName)) {
							//创建了bean,就放入进去,但object可能内容没有完全弄好
							this.factoryBeanObjectCache.put(beanName, object);
						}

然后factoryBeanObjectCache缓存中有该beanName对应的实例,虽然value的值不完整,但肯定不为null。那么这期间B线程再获取上面线程A释放的锁,再一次从缓存获取该beanName对应的value对象,应该是不为null的,那么B线程就直接返回该获取的object了啊。感觉压根不会执行下面第二次缓存获取的代码部分啊

					//调用工厂方法创建bean实例
					object = doGetObjectFromFactoryBean(factory, beanName);
					// Only post-process and store if not put there already during getObject() call above
					// (e.g. because of circular reference processing triggered by custom getBean calls)
					Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
					if (alreadyThere != null) {
						object = alreadyThere;
					}

老师可以再解答这部分的情况吗?感觉好像进不到第二次获取缓存的代码地方。
而如果要让它不缓存,就要再线程A中提前return,估计就是下面的isSingletonCurrentlyInCreation(beanName)判断中为true才行。但是如果线程A释放了锁,该判断应该为false吧,毕竟分别执行了beforeSingletonCreation和afterSingletonCreation这两个函数

						if (shouldPostProcess) {
							if (isSingletonCurrentlyInCreation(beanName)) {
								// Temporarily return non-post-processed object, not storing it yet..
								return object;
							}
							beforeSingletonCreation(beanName);
							try {
								object = postProcessObjectFromFactoryBean(object, beanName);
							}
							catch (Throwable ex) {
								throw new BeanCreationException(beanName,
										"Post-processing of FactoryBean's singleton object failed", ex);
							}
							finally {
								afterSingletonCreation(beanName);
							}
						}
						if (containsSingleton(beanName)) {
							//创建了bean,就放入进去,但object可能内容没有完全弄好
							this.factoryBeanObjectCache.put(beanName, object);
						}
					}
写回答

2回答

慕雪芸茗

2022-06-09

同学你好,我来回答你的疑问,我对Spring的这段也产生过疑问,但是经过我思思思索之后,这里再次获取Bean的原因,根本不是异步,原因有二:

第一:如果异步,那么这个FactoryBean 的 getObject 方法会立即,返回,要么返回null,要么返回一个不完整的对象,这个对象的属性被一个其他线程正在异步初始化,那么回到getObjectFromFactoryBean中,会得到这个对象,然后执行后面的逻辑。因为此处代码加锁,肯定只有一个线程进来。

第二:既然只有一个线程进来,那么同一段代码在期间被另一个线程执行的可能就是当前线程被等待,Object.wait(),但是用户FactoryBean中的getObject要等待当前线程,就必须要获取到singletonObjects这个变量,可惜它是私有的,不可能过个application.getBeanFactory().getSingletonObjects()来获取,那么退一步讲,可以进行反射获取,然后调用等待,然后就可以用别的线程执行了,别的线程可能也会调用application.getBean()来获取bean,而获取的bean又恰好依赖刚刚正要创建的那个bean,那么别的线程就会再次进入这个代码段,因为wait之后会释放锁。等到开始的线程被唤醒后,那么执行

Object alreadyThere = this.factoryBeanObjectCache.get(beanName);

这个检查是有必要的。但是进一步讲,谁没事干,这么费劲,还要反射获取singletonObjects然后休眠当前线程,自己创建线程再去做初始化,,显然,没有意义,更是自己给自己找事。所以这种情况连Spring官方都不屑说明了:

// Only post-process and store if not put there already during getObject() call above
// (e.g. because of circular reference processing triggered by custom getBean calls)

总结:

从上面的注释中可以得知一种情况,源码的作者说了,而且说的很清楚,请仔细品味第二句话,之所以要在此再次判断是否已经创建的原因是用的FactoryBean的getObject方法中可能会再次调用application.getBean方法获取想要的Bean,而这个Bean又恰好依赖正在创建的Bean,那么这个依赖就会被再次创建,因为是同一个线程,synchronize又是可重入锁,所以必然会继续来到这段代码,但是来到这段代码的程序调用堆栈依然是更深了,等执行完,保存好对象后,那么调用栈会依次退出,等退到当前栈的时候,就拿到了FactoryBean.getObject创建的对象了,然后就会再次检查是否已经创建过了:

Object alreadyThere = this.factoryBeanObjectCache.get(beanName);

好了,这才是最终的最正确的真相,很好理解,一般情况下轻易不要往多线程上去想,Spring多线程的防控更多的是对已经启动好的应用获取Scope为Prototype类型的bean的处理

1
0

翔仔

2021-09-06

同学好,这个问题应该是浓缩成对

Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
   object = doGetObjectFromFactoryBean(factory, beanName);
   // Only post-process and store if not put there already during getObject() call above
   // (e.g. because of circular reference processing triggered by custom getBean calls)
   Object alreadyThere = this.factoryBeanObjectCache.get(beanName);

为什么要执行两次

this.factoryBeanObjectCache.get(beanName)

的不解。这里主要是

beforeSingletonCreation
afterSingletonCreation
postProcessObjectFromFactoryBean

都是供外部实现的钩子方法,所以都是不可控的,而如果post-process方法里面是异步去创建bean的,创建完成插入缓存的话,整个创建过程就是不可控的,即不知道啥时候缓存就有值了,同学也可以看看这个注释

// Only post-process and store if not put there already during getObject() call above

也说明了两次调用的原因,主要是担心后置处理器此时已经异步完成对bean的创建并且放入了缓存里

0
3
慕设计0394643
回复
翔仔
好的,了解了。谢谢老师,这个确实,一般使用默认实现,有可能是用户自己的外部类实现。感谢
2021-09-08
共3条回复

剑指Java自研框架,决胜Spring源码

快速入门Spring核心源码+从零开发自研框架

1498 学习 · 495 问题

查看课程