Spring官网阅读(七)容器的扩展点(二)FactoryBean
在上篇文章中我们已经对容器的第一个扩展点(
BeanFactoryPostProcessor
)做了一系列的介绍。其中主要介绍了Spring容器中BeanFactoryPostProcessor
的执行流程。已经Spring自身利用了BeanFactoryPostProcessor
完成了什么功能,对于一些细节问题可能说的不够仔细,但是在当前阶段我想要做的主要是为我们以后学习源码打下基础。所以对于这些问题我们暂且不去过多纠结,待到源码学习阶段我们会进行更加细致的分析。在本篇文章中,我们将要学习的是容器的另一个扩展点(
FactoryBean
),对于FactoryBean
官网上的介绍甚短,但是如果我们对Spring的源码有一定了解,可以发现Spring在很多地方都对这种特殊的Bean做了处理。话不多说,我们开始进入正文。
文章目录
我们还是先看看官网上是怎么说的:
官网介绍
从上面这段文字我们可以得出以下几个信息:
FactoryBean
主要用来定制化Bean的创建逻辑- 当我们实例化一个Bean的逻辑很复杂的时候,使用
FactoryBean
是很必要的,这样可以规避我们去使用冗长的XML配置 FactoryBean
接口提供了以下三个方法:
Object getObject()
: 返回这个FactoryBean
所创建的对象。boolean isSingleton()
: 返回FactoryBean
所创建的对象是否为单例,默认返回true。Class getObjectType()
: 返回这个FactoryBean
所创建的对象的类型,如果我们能确认返回对象的类型的话,我们应该正常对这个方法做出实现,而不是返回null。
- Spring自身大量使用了
FactoryBean
这个概念,至少有50个FactoryBean
的实现类存在于Spring容器中 - 假设我们定义了一个
FactoryBean
,名为myFactoryBean
,当我们调用getBean("myFactoryBean")
方法时返回的并不是这个FactoryBean
,而是这个FactoryBean
所创建的Bean,如果我们想获取到这个FactoryBean
需要在名字前面拼接"&",行如这种形式:getBean("&myFactoryBean")
上面这些概念可能刚刚说的时候大家不是很明白,下面我们通过FactoryBean
的一些应用来进一步体会这个接口的作用。
FactoryBean的应用
我们来看下面这个Demo:
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
System.out.println("执行了一段复杂的创建Bean的逻辑");
return new TestBean();
}
@Override
public Class<?> getObjectType() {
return TestBean.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
public class TestBean {
public TestBean(){
System.out.println("TestBean被创建出来了");
}
}
// 测试类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac=
new AnnotationConfigApplicationContext(Config.class);
System.out.println("直接调用getBean(\"myFactoryBean\")返回:"+ac.getBean("myFactoryBean"));
System.out.println("调用getBean(\"&myFactoryBean\")返回:"+ac.getBean("&myFactoryBean"));
}
}
运行后结果如下:
执行了一段复杂的创建Bean的逻辑
TestBean被创建出来了
直接调用getBean(“myFactoryBean”)返回:com.dmz.official.extension.factorybean.TestBean@28f67ac7
调用getBean("&myFactoryBean")返回:com.dmz.official.extension.factorybean.MyFactoryBean@256216b3
我们虽然没有直接将TestBean
放入Spring容器中,但是通过FactoryBean
也完成了这一操作。同时当我们直接调用getBean("FactoryBean的名称")
获取到的是FactoryBean
创建的Bean,但是添加了“&”后可以获取到FactoryBean
本身。
FactoryBean相关源码分析
我们先看下下面这张图:
涉及到FactoryBean
主要在3-11-6
这一步中,我们主要关注下面这段代码:
// .....省略无关代码.......
// 1.判断是不是一个FactoryBean
if (isFactoryBean(beanName)) {
// 2.如果是一个FactoryBean那么在getBean时,添加前缀“&”,获取这个FactoryBean
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
// 3.做权限校验,判断是否是一个SmartFactoryBean,并且不是懒加载的
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
// 3.判断是否是一个SmartFactoryBean,并且不是懒加载的
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
// 4.如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean
getBean(beanName);
}
}
}
else {
// 不是一个FactoryBean,直接创建这个Bean
getBean(beanName);
}
// ...省略无关代码.....
我们按照顺序一步步分析,首先看第一步:
- 判断是不是一个
FactoryBean
,对应源码如下:
public boolean isFactoryBean(String name) throws NoSuchBeanDefinitionException {
String beanName = transformedBeanName(name);
// 直接从单例池中获取这个Bean,然后进行判断,看是否是一个FactoryBean
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null) {
return (beanInstance instanceof FactoryBean);
}
// 查找不到这个BeanDefinition,那么从父容器中再次确认是否是一个FactoryBean
if (!containsBeanDefinition(beanName) && getParentBeanFactory() instanceof ConfigurableBeanFactory) {
// No bean definition found in this factory -> delegate to parent.
return ((ConfigurableBeanFactory) getParentBeanFactory()).isFactoryBean(name);
}
// 从当前容器中,根据BeanDefinition判断是否是一个FactoryBean
return isFactoryBean(beanName, getMergedLocalBeanDefinition(beanName));
}
- 如果是一个
FactoryBean
那么在getBean
时,添加前缀“&”,获取这个FactoryBean
- 判断是否是一个
SmartFactoryBean
,并且不是懒加载的
这里涉及到一个概念,就是SmartFactoryBean
,实际上这个接口继承了FactoryBean
接口,并且SmartFactoryBean
是FactoryBean
的唯一子接口,它扩展了FactoryBean
多提供了两个方法如下:
// 是否为原型,默认不是原型
default boolean isPrototype() {
return false;
}
// 是否为懒加载,默认为懒加载
default boolean isEagerInit() {
return false;
}
从上面的代码中可以看出,我们当当实现一个FactoryBean
接口,Spring并不会在启动时就将这个FactoryBean
所创建的Bean创建出来,为了避免这种情况,我们有两种办法:
- 实现
SmartFactoryBean
,并重写isEagerInit
方法,将返回值设置为true - 我们也可以在一个不是懒加载的Bean中注入这个
FactoryBean
所创建的Bean,Spring在解决依赖关系也会帮我们将这个Bean创建出来
实际上我们可以发现,当我们仅仅实现FactoryBean
时,其getObject()
方法所产生的Bean,我们可以当前是懒加载的。
- 如果是一个
SmartFactoryBean
并且不是懒加载的,那么创建这个FactoryBean
创建的Bean。这里需要注意的是此时创建的不是这个FactoryBean
,以为在getBean
时并没有加一个前缀“&”,所以获取到的是其getObject()
方法所产生的Bean。
在上面的代码分析完后,在3-6-11-2
中也有两行FactoryBean
相关的代码,如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 1.获取bean名称
final String beanName = transformedBeanName(name);
Object bean;
//...省略无关代码...,这里主要根据beanName创建对应的Bean
// 2.调用getObject对象创建Bean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
- 获取bean名称
protected String transformedBeanName(String name) {
// 這個方法主要用來解析別名,如果是別名的話,获取真实的BeanName
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
// 处理FactoryBean
public static String transformedBeanName(String name) {
Assert.notNull(name, "'name' must not be null");
// 没有带“&”,直接返回
if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
return name;
}
// 去除所有的“&”,防止这种写法getBean("&&&&beanName")
return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
do {
beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
}
while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
return beanName;
});
}
- 如果是一个
FactoryBean
,将会调用其getObject()
方法,如果不是直接返回。
我们可以看到,在调用getObjectForBeanInstance(sharedInstance, name, beanName, null);
传入了一个参数—name,也就是还没有经过transformedBeanName
方法处理的bean的名称,可能会带有“&”符号,Spring通过这个参数判断这个Bean是不是一个FactoryBean
,如果是的话,会调用其getObject()
创建Bean。**被创建的Bean不会存放于单例池中,而是放在一个名为factoryBeanObjectCache
的缓存中。**具体的代码因为比较复杂,在这里我们就暂且不分析了,大家可以先留个印象,源码阶段我会做详细的分析。
Spring中FactoryBean概念的汇总(纯粹个人观点)
除了我们在上文中说到的实现了FactoryBean
或者SmartFactoryBean
接口的Bean可以称之为一个”FactoryBean
“,不知道大家对BeanDefinition
中的一个属性是否还有印象。BeanDefinition
有属性如下(实际上这个属性存在于AbstractBeanDefinition
中):
@Nullable
private String factoryBeanName;
@Nullable
private String factoryMethodName;
对于这个属性跟我们这篇文章中介绍的FactoryBean
有什么关系呢?
首先,我们看看什么情况下bd
中会存在这个属性,主要分为以下两种情况:
第一种情况:
@Configuration
public class Config {
@Bean
public B b(){
return new B();
}
}
我们通过@Bean
的方式来创建一个Bean,那么在B的BeanDefinition
会记录factoryBeanName
这个属性,同时还会记录是这个Bean中的哪个方法来创建B的。在上面的例子中,factoryBeanName
=config
,factoryMethodName
=b。
第二种情况:
<bean id="factoryBean" class="com.dmz.official.extension.factorybean.C"/>
<bean id="b" class="com.dmz.official.extension.factorybean.B" factory-bean="factoryBean" factory-method="b"/>
通过XML的方式进行配置,此时B的BeanDefinition
中factoryBeanName
=factoryBean
,factoryMethodName
=b。
上面两种情况,BeanDefinition
中的factoryBeanName
这个属性均不会为空,但是请注意此时记录的这个名字所以对于的Bean并不是一个实现了FactoryBean
接口的Bean。
综上,我们可以将Spring中的FactoryBean
的概念泛化,也就是说所有生产对象的Bean我们都将其称为FactoryBean
,那么可以总结画图如下:
这是个人观点哈,没有在官网找到什么文档,只是这种比较学习更加能加深印象,所以我把他们做了一个总结,大家面试的时候不用这么说
跟FactoryBean相关常见的面试题
1、FactoryBean跟BeanFactory的区别
FactoryBean
就如我们标题所说,是Spring提供的一个扩展点,适用于复杂的Bean的创建。mybatis
在跟Spring做整合时就用到了这个扩展点。并且FactoryBean
所创建的Bean跟普通的Bean不一样。我们可以说FactoryBean
是Spring创建Bean的另外一种手段。
而BeanFactory
是什么呢?BeanFactory
是Spring IOC
容器的顶级接口,其实现类有XMLBeanFactory
,DefaultListableBeanFactory
以及AnnotationConfigApplicationContext
等。BeanFactory
为Spring管理Bean提供了一套通用的规范。接口中提供的一些方法如下:
boolean containsBean(String beanName)
Object getBean(String)
Object getBean(String, Class)
Class getType(String name)
boolean isSingleton(String)
String[] getAliases(String name)
通过这些方法,可以方便地获取bean,对Bean进行操作和判断。
2、如何把一个对象交给Spring管理
首先,我们要弄明白一点,这个问题是说,怎么把一个对象交給Spring管理,“对象”要划重点,我们通常采用的注解如@Compent
或者XML配置这种类似的操作并不能将一个对象交给Spring管理,而是让Spring根据我们的配置信息及类信息创建并管理了这个对象,形成了Spring中一个Bean。把一个对象交给Spring管理主要有两种方式
- 就是用我们这篇文章中的主角,
FactoryBean
,我们直接在FactoryBean
的getObject
方法直接返回需要被管理的对象即可 @Bean
注解,同样通过@Bean
注解标注的方法直接返回需要被管理的对象即可。
总结
在本文中我们完成了对FactoryBean
的学习,最重要的是我们需要明白一点,FactoryBean
是Spring中特殊的一个Bean,Spring利用它提供了另一种创建Bean的方式,FactoryBean
整体的体系比较复杂,FactoryBean
是如何创建一个Bean的一些细节我们还没有涉及到,不过不要急,在源码学习阶段我们还会接触到它,并会对其的整个流程做进一步的分析。目前容器的扩展点我们还剩最后一个部分,即BeanPostProcessor
。BeanPostProcessor
贯穿了整个Bean的生命周期,学习的难度更大。希望大家跟我一步步走下去,认认真真学习完Spring,加油!
Spring官网阅读(七)容器的扩展点(二)FactoryBean的更多相关文章
- Spring官网阅读 | 总结篇
接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...
- Spring官网阅读(十七)Spring中的数据校验
文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...
- Spring官网阅读(十六)Spring中的数据绑定
文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...
- Spring官网阅读(十八)Spring中的AOP
文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...
- Spring官网阅读(一)容器及实例化
从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...
- Spring官网阅读(三)自动注入
上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...
- Spring官网阅读(二)(依赖注入及方法注入)
上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...
- Spring官网阅读(八)容器的扩展点(三)(BeanPostProcessor)
在前面两篇关于容器扩展点的文章中,我们已经完成了对BeanFactoryPostProcessor很FactoryBean的学习,对于BeanFactoryPostProcessor而言,它能让我们对 ...
- Spring官网阅读(六)容器的扩展点(一)BeanFactoryPostProcessor
之前的文章我们已经学习完了BeanDefinition的基本概念跟合并,其中多次提到了容器的扩展点,这篇文章我们就开始学习这方面的知识.这部分内容主要涉及官网中的1.8小结.按照官网介绍来说,容器的扩 ...
随机推荐
- Java成长第五集--面向对象设计的五大原则
S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写.以下图说明: 下面就个人的理解来说说这五大原则的含义到 ...
- AJ整理问题之:NSTimer准确吗?
NSTimer准确吗? 问题:NSTimer准确吗?如果不准确,怎么办? NSTimer的工作原理:假设timer每隔一段时间执行一次事件,很均匀的(例如每隔多少秒),假设在某一时刻cpu在做疯狂的大 ...
- Juli函数
- JUC强大的辅助类讲解--->>>CountDownLatchDemo (减少计数)
原理: CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞.其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞), ...
- G - Harmonic Number (II) LightOJ - 1245
算是一个找规律的题目吧. 枚举前sqrt(n)个数,数i出现的次数为n/i-n/(i+1),对答案的贡献为(n/i-n/(i+1))*i. 对于sqrt后边的数,可以直接由n/i获得,并且一定只出现一 ...
- 常见分布式全局唯一ID生成策略
全局唯一的 ID 几乎是所有系统都会遇到的刚需.这个 id 在搜索, 存储数据, 加快检索速度 等等很多方面都有着重要的意义.工业上有多种策略来获取这个全局唯一的id,针对常见的几种场景,我在这里进行 ...
- 新的知识点来了-ES6 Proxy代理 和 去银行存款有什么关系?
ES给开发者提供了一个新特性:Proxy,就是代理的意思.也就是我们这一节要介绍的知识点. 以前,ATM还没有那么流行的时候(暴露年纪),我们去银行存款或者取款的时候,需要在柜台前排队,等柜台工作人员 ...
- nignx location index的用法
来源:https://blog.csdn.net/qq_32331073/article/details/81945134#_10 index指令的作用 在前后端分离的基础上,通过Nginx配置,指定 ...
- MySql -- 数据结构
现在的数据表不单单只是存储数据,还有的是设计功能和快速处理数据的结构功能: 首先,我们在设计数据库的时候,我们要先分清楚,那些是要单纯的存储数据的(固定),然后再设计出来数据的表(流动) 你懂我意思吧 ...
- HTML H5响应式,表格,表单等
HTML杂项 响应式图片 <img srcset="elva-fairy-320w.jpg 320w, elva-fairy-480w.jpg 480w, elva-fairy-800 ...