创作不易,转载请篇首注明 作者:掘金@小希子 + 来源链接~
如果想了解更多Spring源码知识,点击前往其余逐行解析Spring系列
一、前言
Spring
的IOC
部分已经差不多讲完了,下一篇会开始讲AOP
部分的源码。本篇博文主要是分享一个小甜点给同学们,讲一下FactoryBean
这个接口。
这个接口我们日常开发中使用的不多,更多的是第三方框架接入Spring
的时候会使用。不过由于这个接口跟我们IOC
中承载主要逻辑的BeanFactory
长的比较像,所以面试的时候面试官偶尔也会问问这两种有什么区别。
要我说,这两者区别可大了去了,因为他们基本没啥关联,完全是两个东西。
二、FactoryBean
的作用
在讲原理之前,我们还是简单的讲一下FactoryBean
接口的作用。我们首先看一下FactoryBean
的定义:
1 | java复制代码public interface FactoryBean<T> { |
然后我们写一个类实现一下这个接口:
1 | java复制代码public class SubBean { |
我们启动Spring打印一下这个factoryBeanDemo
:
1 | java复制代码public void test() { |
输出:
1 | shell复制代码com.xiaoxizi.spring.factoryBean.SubBean@3e0e1046 |
可以看到,我们通过getBean("factoryBeanDemo")
拿到的居然是FactoryB
的实例,而不是我们@Service
注解标记的FactoryBeanDemo
的实例。
这就是FactoryBean
接口的用途啦,当我们向spring
注册一个FactoryBean
时,通过beanName
获取到的将是FactoryBean#getObject
方法返回的subBean
(我们使用subBean
来表示factoryBean#getObject
的返回对象)实例,而且注意看FactoryBean#isSingleton
方法,说明我们也是可以指定getObject
方法获取的实例是单例的还是多例的。
那么,在这种情况,我们还能获取到FactoryBeanDemo
的实例么?当然也是可以的,只不过我们需要稍微做一点改变:
1 | java复制代码public void test() { |
输出:
1 | shell复制代码com.xiaoxizi.spring.factoryBean.SubBean@3e0e1046 |
也就是说,正常通过beanName
从Spring
容器中取的话,是只能取到subBean
实例的,但是如果在beanName
前面加上&
符号,使用&beanName
从Spring
容器中获取,才能获取到FactoryBean
实例本身。
三、源码解析
那么Spring
是如何支撑FactoryBean
的功能的呢?我们还是一起跟源码看一下。我们之前讲bean
的生命周期的时候,有讲到单例的bean
都是在Spring
容器启动的时候就初始化的,那么对于FactoryBean
实例,它的FactoryBean#getObject
方法也会在Spring
容器启动的时候就初始化嘛?subBean
实例又储存在哪里呢?带着这些疑问,我们来看一下获取bean
的核心逻辑AbstractBeanFactory#doGetBean
方法:
1 | java复制代码protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, |
我们可以看到,不管是初始化还是
1. transformedBeanName
处理&
符号
刚刚在测试的时候,我们有看到,使用getBean("&factoryBeanDemo")
是可以获取到factoryBean
的实例的,那么对于这个&
符号,spring
是在transformedBeanName
中做初步处理的:
1 | java复制代码// AbstractBeanFactory#transformedBeanName |
也就是说,对于我们getBean("&factoryBeanDemo")
的调用,经过transformedBeanName(name)
这一步之后,返回的beanName
就是"factoryBeanDemo"
了。
2. getObjectForBeanInstance
获取最终需要返回的bean
实例
不管是调用getBean
时,是触发创建初始化bean
流程(单例容器初始化/多例每次调用都会创建bean
实例),还是直接从一级缓存获取到单例实例最终都需要使用获取到的bean
实例调用getObjectForBeanInstance
获取最终需要返回的bean
,而我们的FactoryBean
的逻辑就是在这个地方处理的:
1 | java复制代码// 需要注意的是,这里传入了name和beanName两个值 |
可以看到,如果a
实例是一个factoryBean
的话,当我们调用getBean("a")
时,是会创建a
实例并触发它的factoryBean#getObject
获取到subBean
实例并返回的;而如果是使用getBean("&a")
,则只会实例化a
实例并返回factoryBean
本身。
2.1. getCachedObjectForFactoryBean
从缓存获取subBean
可以看到,当调用getObjectForBeanInstance
方法的最后一个参数BeanDefinition
为空的时候,代表factoryBean
实例是已经创建好了,这个时候会通过getCachedObjectForFactoryBean
方法尝试直接从缓存中获取subBean
对象,这个方法的逻辑很简单:
1 | java复制代码// 当前类是 FactoryBeanRegistrySupport |
如果缓存中有subBean
实例,就直接返回这个实例,如果没有,则还会继续走下面的获取subBean
的逻辑。
2.2. getObjectFromFactoryBean
从factoryBean
获取subBean
假设缓存中还没有subBean
实例,那么肯最终都会走到getObjectFromFactoryBean
方法,来获取一个subBean
对象:
1 | java复制代码protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { |
我们简单看一下这个postProcessObjectFromFactoryBean
:
1 | java复制代码protected Object postProcessObjectFromFactoryBean(Object object, String beanName) { |
可以看到,其实最终调用到的applyBeanPostProcessorsAfterInitialization
方法就是bean
初始化流程中,initializeBean
方法里,bean
完全初始化完之后调用的埋点方法。aop
也是在这个埋点做操作的,所以我们的subBean
也是能使用aop
的功能的。
3. subBean
的初始化时机
我们已经了解了getBean
中对factoryBean
的处理逻辑,简单的来讲,其实就是针对传入的name
是否有&
前缀,来走不同的分支逻辑。
那么现在又有一个问题了,单例的subBean
对象,到底是在什么时候创建并且被spring
管理起来的呢?
我们知道,如果subBean
缓存factoryBeanObjectCache
中没有对于的subBean
,那么直接调用getBean("factoryBeanDemo")
肯定是会创建一个subBean
的,现在我想说的是,我们普通的单例bean
会在spring
容器启动的时候就初始化,单例的subBean
也会在这个时候初始化么?要清楚这个问题,我们还是要直接去看源码,这个时候就需要看spring
容器启动时,初始化所有单例bean
的逻辑了:
如果对
bean
的生命周期级spring
容器启动流程不熟悉,《逐行解读Spring(四) - 万字长文讲透bean生命周期(上)(下)》
1 | java复制代码// DefaultListableBeanFactory类中 |
可以看到,对于我们的普通的subBean
,在spring
容器启动的时候,是不会主动去初始化的,而只会初始化factoryBean
对象。除非我们的factoryBean
实现了FactoryBean
的子接口SmartFactoryBean
并表明该subBean
需要提前初始化。
也简单看一下SmartFactoryBean
接口的定义:
1 | java复制代码public interface SmartFactoryBean<T> extends FactoryBean<T> { |
4. subBean
的循环依赖问题
我们之前讲循环依赖的时候,都是基于两个普通的bean
来讲解的,而循环依赖现象是指spring
在进行单例bean
的依赖注入时,出现A->B
,B->A
的问题。
同学们可能会说,subBean
的依赖注入都不归spring
管理了,怎么还能出现循环依赖问题的?
首先需要明确一点的是,循环依赖其实跟spring
没有关系的,只要出现了A->B
,B->A
的情况,我们就认为A
、B
实例出现了循环依赖。而spring
只是在它的管理的范围内,巧妙的使用了三级缓存/@Lazy
解决了循环依赖而已。
而由于factoryBean
实例本身就是由spring
容器管理的,那么我们做以下操作,也是合理的:
1 | java复制代码@Getter |
我们factoryBean
通过BeanFactoryAware
接口拿到beanFactory
实例,并且在工厂方法getObject
获取subBean
的流程中使用beanFactory.getBean(A.class)
从spring
容器中获取a
实例,而a
实例又是依赖subBean
实例的…
有同学可能会觉得我在难为spring
,为什么要强行用这么复杂的结构,来构建一个循环依赖呢?
可是a
实例和subBean
最终不都是由spring
管理么?它不应该解决这个问题么?
当然是可以解决的,不过这个地方要分两种情况讨论。
接下来的讨论会涉及到
spring
三级循环依赖的原理,不清楚的同学可以去《逐行解读Spring(五)- 没人比我更懂循环依赖!》了解相关知识。
3.1. 先初始化a
实例
对于先初始化a
实例的场景,其实spring
原有的三级缓存设计就可以很好的解决这个问题。同学们可以回想一下,我们在创建a
实例之后,尚未进行依赖注入subBean
之前,就把a实例暴露到缓存了。而注入subBean
的时候,会触发FactoryBean#getObject
方法,最终会调用到我们自己写的beanFactory.getBean(A.class)
的逻辑,从缓存中获取到暴露到缓存的a
实例。
那么按这个流程下来,其实整体是没问题的,spring
的三级缓存的设计已经很好的解决了这种循环依赖的问题。
我们还是简单的看一下流程图:
3.2. 先初始化subBean
实例
刚刚讲subBean
的初始化时机时,其实有讲过,正常的subBean
的初始化是一种类似于懒加载的方式,也就是说它是不会在a
初始化化之前触发初始化的。但是有时候我们的项目中,实例的依赖关系可能不是这么清晰的。
假设我们有一个c
实例,它依赖subBean
实例,而subBean
实例又和a
实例循环依赖。那如果c
实例先于a
实例初始化,就会出现subBean
实例先于a
实例初始化的情况了。由于我们的subBean
是没有多级缓存的机制来解决循环依赖问题,那么这个时候,整个初始化流程就变成了:
可以看到,如果没有特殊处理的话,尽管由于我们的普通bean
有三级缓存的设计,不会出现完全无法解决的级联创建实例问题。但是,也会导致我们的factoryBean#getObject
被调用两次,生成两个subBean
对象,且最终factoryBeanObjectCache
缓存中的subBean1
对象与a
实例中注入的subBean2
对象不是同一个。
那么这个情况应该如何解决呢?有同学可能会说,使用多级缓存呀,和普通的bean
一个思路就可以了。
但是,多级缓存的思路,其实主要就是在bean
实例创建之后,依赖注入之前,将bean
实例暴露到缓存中,进而解决循环依赖的问题。然而,我们刚刚举例中,实际是在factoryBean#getObject
获取subBean
实例的过程中进行了依赖注入(虽然是我们手动的调用beanFactory.getBean
获取的依赖),这个情况其实有点类似于构造器注入依赖了,构造器循环依赖用多级缓存的思想也解决不了哇。那么对于两个subBean
实例的问题,spring
是怎么解决的呢?spring
通过短短几行代码,就解决了这个问题:
1 | java复制代码protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { |
这里再factoryBean#getObject
方法获取到subBean1
之后,再次从factoryBeanObjectCache
获取了一遍subBean
实例,如果获取到了subBean2
,其实就代表出现我们举例的那种循环依赖了,导致缓存中已经有subBean
实例了。此时会把subBean2
赋值给object
并且返回出去,subBean1
就直接丢弃掉了,也不会放入缓存。这样就巧妙的解决了两个subBean
的问题啦~
3.3. 无法解决的循环依赖问题
刚刚我们有聊到,factoryBean#getObject
中使用beanFactory#getBean
进行依赖注入,本质上相当于是构造器注入。
而上一篇讲循环缓存的时候,我们也有讲过,正常情况下来讲,构造器循环依赖是无法解决的:
1 | java复制代码@Getter |
我们启动一下:
1 | java复制代码public void test() { |
肯定是会直接报错的:
1 | shell复制代码Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference? |
当然我们还是可以使用@Lazy
解决这个问题:
1 | java复制代码@Component |
这种情况下,spring
是能正常运行的,因为我们使用@Lazy
切断了循环依赖链。
那么接下来我要说的是,正真完全无法解决的循环依赖问题:
1 | java复制代码@AllArgsConstructor |
这种情况下启动会直接栈溢出的,连BeanCurrentlyInCreationException
异常都不会有。主要原因是spring
是在调用完factoryBean#getObject
之后再使用singletonsCurrentlyInCreation
容器进行循环依赖检测的,而这种循环依赖,其实是在疯狂的调用factoryBeanA#getObject
-> factoryBeanB#getObject
-> factoryBeanA#getObject
-> ...
了,直接导致了栈溢出。
1 | java复制代码protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) { |
所以,同学们千万不要写刚刚示例中这种代码呀,一定一定会被开除哒~
四、小结
本篇博文主要讲了一下spring
中的FactoryBean
接口。这个接口其实是spring
对工厂模式的一种支持。
通过阅读源码,我们知道了:
- 单例的
factoryBean
对象本身会在spring
容器启动时主动初始化。而subBean
的初始化则是在第一次需要获取时才会触发。 - 如果
factoryBean
对象实现的接口是SmartFactoryBean
且isEagerInit
方法返回true
,那么subBean
对象也会在spring
容器启动的时候主动初始化。 - 如果
bean
注册的时候,beanName
对应的bean
实例是一个factoryBean
,那么我们通过getBean(beanName)
获取到的对象将会是subBean
对象;如果要获取工厂对象factoryBean
,需要使用getBean("&" + beanName)
. - 单例的
subBean
也会缓存在spring
容器中,具体的容器是FactoryBeanRegistrySupport#factoryBeanObjectCache
,一个Map<beanName, subBean实例>
。 spring
的三级缓存设计解决了大部分循环依赖问题,而对与subBean
与普通bean
的循环依赖导致可能出现两个subBean
对象的问题,spring
采用多重检查的方式,丢弃掉其中一个无用的subBean
,保留已被其他bean
注入的那个subBean
实例。- 两个不同的
subBean
的获取逻辑factoryBean#getObject
中的相互循环依赖是无法解决的,因为这种注入对spring
来讲有点类似于构造器注入,也就是说这种循环依赖是构造器循环依赖,而且无法使用@Lazy
强行切断,所以一定不要写这种代码。
创作不易,转载请篇首注明 作者:掘金@小希子 + 来源链接~
如果想了解更多Spring源码知识,点击前往其余逐行解析Spring系列
٩(* ఠO ఠ)=3⁼³₌₃⁼³₌₃⁼³₌₃嘟啦啦啦啦。。。
这里是新人博主小希子,大佬们都看到这了,左上角点个赞再走吧~~
本文转载自: 掘金