所有Bean的父亲,解密Spring容器中的FactoryBean接口
文章目录
一、前言
二、FactoryBean概要
-
FactoryBean主要用来定制化Bean的创建逻辑。
-
当我们实例化一个Bean的逻辑很复杂的时候,使用FactoryBean是很必要的,这样可以规避我们去使用冗长的XML配置。
-
FactoryBean接口提供了以下三个方法:
Object getObject(): 返回这个FactoryBean所创建的对象。
boolean isSingleton(): 返回FactoryBean所创建的对象是否为单例,默认返回true。
Class getObjectType(): 返回这个FactoryBean所创建的对象的类型,如果我们能确认返回对象的类型的话,我们应该正常对这个方法做出实现,而不是返回null。
1、Spring自身大量使用了FactoryBean这个概念,至少有50个FactoryBean的实现类存在于Spring容器中。
2、假设我们定义了一个FactoryBean,名为myFactoryBean,当我们调用getBean(“myFactoryBean”)方法时返回的并不是这个FactoryBean,而是这个FactoryBean所创建的Bean,如果我们想获取到这个FactoryBean需要在名字前面拼接"&“,行如这种形式:getBean(”&myFactoryBean")。
三、使用层面: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接口
4.1 从spring启动开始
我们先看下下面这张图:
涉及到FactoryBean主要在3-11-6这一步中,我们主要关注下面这段代码:
4.2 getBean()
// .....省略无关代码.......
// 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);
}
// ...省略无关代码.....
金手指:isEagerInit 是否渴望初始化,true 表示不使用懒加载,false 表示使用懒加载。
我们按照顺序一步步分析,首先看第一步:
4.2.1 isFactoryBean()
判断是不是一个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,并且不是懒加载的
4.2.2 SmartFactoryBean接口(FactoryBean接口的唯一子接口)
这里涉及到一个概念,就是SmartFactoryBean,实际上这个接口继承了FactoryBean接口,并且SmartFactoryBean是FactoryBean的唯一子接口,它扩展了FactoryBean多提供了两个方法如下:
// 是否为原型,默认不是原型
default boolean isPrototype() {
return false;
}
// 是否为懒加载,默认为懒加载
default boolean isEagerInit() {
return false;
}
4.2.3 存在的问题:Spring启动默认不会将自定义FactoryBean接口的实现类作为bean,注入到spring ioc容器中
从上面的代码中可以看出,当自定义类实现一个FactoryBean接口,Spring并不会在启动时就将这个FactoryBean所创建的Bean创建出来,为了避免这种情况,我们有两种办法:
- 方法一:实现SmartFactoryBean,并重写isEagerInit方法,将返回值设置为true(金手指:解释:如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean,getBean(beanName);)
- 方法二:我们也可以在一个不是懒加载的Bean中注入这个FactoryBean所创建的Bean,Spring在解决依赖关系也会帮我们将这个Bean创建出来。
实际上我们可以发现,当我们仅仅实现FactoryBean接口时,其getObject()方法所产生的Bean,我们可以当前是懒加载的。
如果是一个SmartFactoryBean并且不是懒加载的,那么创建这个FactoryBean创建的Bean。这里需要注意的是此时创建的不是这个FactoryBean,以为在getBean时并没有加一个前缀“&”,所以获取到的是其getObject()方法所产生的Bean。
在上面的代码分析完后,在3-6-11-2中也有两行FactoryBean相关的代码,如下:
4.3 doGetBean()
4.3.1 doGetBean()
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);
}
4.3.2 transformedBeanName() 获取bean名称
获取bean名称
protected String transformedBeanName(String name) {
// 這個方法主要用來解析別名,如果是別名的話,获取真实的BeanName
return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}
// 处理FactoryBean
public static String transformedBeanName(String name) {
// 没有带“&”,直接返回
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的缓存中。**具体的代码因为比较复杂,在这里我们就暂且不分析了。
4.4 BeanDefinition接口也可以称为一个FactoryBean
除了我们在上文中说到的实现了FactoryBean或者SmartFactoryBean接口的Bean可以称之为一个”FactoryBean“,不知道大家对BeanDefinition中的一个属性是否还有印象。
4.4.1 BeanDefinition接口中的两个属性
BeanDefinition有属性如下(实际上这个属性存在于AbstractBeanDefinition中):
@Nullable
private String factoryBeanName;
@Nullable
private String factoryMethodName;
对于这个属性跟我们这篇文章中介绍的FactoryBean有什么关系呢?
4.4.2 @Configuration注解标注的配置类,设置factoryBeanName和factoryMethodName
首先,我们看看什么情况下BeanDefinition接口中会存在这个属性,主要分为以下两种情况:
第一种情况:
@Configuration
public class Config {
@Bean
public B b(){
return new B();
}
}
我们通过@Bean的方式来创建一个Bean,那么在B的BeanDefinition会记录factoryBeanName这个属性,同时还会记录是这个Bean中的哪个方法来创建B的。在上面的例子中,factoryBeanName=config,factoryMethodName=b。
4.4.3 xml配置方式,设置factoryBeanName和factoryMethodName
第二种情况:
<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,那么可以总结画图如下:
这是个人观点哈,没有在官网找到什么文档,只是这种比较学习更加能加深印象,所以我把他们做了一个总结,大家面试的时候不用这么说
五、面试金手指(面试金手指)
5.1 介绍一个FactoryBean这个扩展点/介绍一下FactoryBean这个接口
FactoryBean是Spring提供的一个扩展点,适用于复杂的Bean的创建。mybatis在跟Spring做整合时就用到了这个扩展点。并且FactoryBean所创建的Bean跟普通的Bean不一样。我们可以说FactoryBean是Spring创建Bean的另外一种手段。
-
FactoryBean主要用来定制化Bean的创建逻辑。
-
当我们实例化一个Bean的逻辑很复杂的时候,使用FactoryBean是很必要的,这样可以规避我们去使用冗长的XML配置。
-
FactoryBean接口提供了以下三个方法:
Object getObject(): 返回这个FactoryBean所创建的对象。
boolean isSingleton(): 返回FactoryBean所创建的对象是否为单例,默认返回true。
Class getObjectType(): 返回这个FactoryBean所创建的对象的类型,如果我们能确认返回对象的类型的话,我们应该正常对这个方法做出实现,而不是返回null。
1、Spring自身大量使用了FactoryBean这个概念,至少有50个FactoryBean的实现类存在于Spring容器中。
2、假设我们定义了一个FactoryBean,名为myFactoryBean,当我们调用getBean(“myFactoryBean”)方法时返回的并不是这个FactoryBean,而是这个FactoryBean所创建的Bean,如果我们想获取到这个FactoryBean需要在名字前面拼接"&“,行如这种形式:getBean(”&myFactoryBean")。
5.2 两种方式将bean注入到spring ioc容器中
首先,我们要弄明白一点,这个问题是说,怎么把一个对象交給Spring管理,“对象”要划重点,我们通常采用的注解如@Compent或者XML配置这种类似的操作并不能将一个对象交给Spring管理,而是让Spring根据我们的配置信息及类信息创建并管理了这个对象,形成了Spring中一个Bean。把一个对象交给Spring管理主要有两种方式
- 就是用我们这篇文章中的主角,FactoryBean,我们直接在FactoryBean的getObject方法直接返回需要被管理的对象即可
- @Bean注解,同样通过@Bean注解标注的方法直接返回需要被管理的对象即可。
总结
通过这些方法,可以方便地获取bean,对Bean进行操作和判断。
5.3 附: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进行操作和判断。
六、总结
【Spring扩展点】FactoryBean接口,完成。
天天打码,天天进步!!!
相关文章
- Spring的学习笔记(十五)——SSM 解决接口跨域问题
- Spring学习笔记(十八)——spring日志框架的配置和使用
- batch spring 重复执行_Spring Batch批处理
- Spring中框架
- Spring Boot的其它特性
- 在Spring Cloud 中使用Zipkin
- Spring Cloud:第一章:基本介绍
- 解释spring框架中bean的生命周期_Spring bean的生命周期
- Spring Batch批处理框架,真心强啊!!
- 【SSM】学习笔记(一)—— Spring入门
- 如何使用Spring和Java配置构建一个REST API
- Spring Boot - ApplicationRunner && CommandLineRunner扩展接口
- Spring Boot的测试工具和技巧(一)
- Spring Boot的性能优化(二)
- spring的IOC 容器管理 Bean详解编程语言
- 使用drools6+spring+Drools Workbench+activemq搭建风险控制系统详解编程语言
- Spring Boot2.0之全局捕获异常详解编程语言
- Spring Boot(二十):使用spring-boot-admin对spring-boot服务进行监控详解编程语言