浅谈使用实现FactoryBean接口的方式定义Bean
Posted 默辨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈使用实现FactoryBean接口的方式定义Bean相关的知识,希望对你有一定的参考价值。
在定义一个Bean的时候,我们可以直接实现FactoryBean接口,然后重写对应的getXxx方法,就能够完成一个Bean的定义工作
一、使用实现接口的方式定义Bean
Bean定义代码:
@Component
public class Teacher implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new Teacher();
}
@Override
public Class<?> getObjectType() {
return Teacher.class;
}
}
配置类代码:
@ComponentScan(basePackages = {"pers.mobian.springfifth"})
public class ConfigBean {
}
测试类代码:
public class Test3 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
System.out.println(context.getBean("teacher"));
}
}
二、其实是生成了两个Bean
我们不难发现对应的getXxx方法是可以自定义返回类型的,如果我们getObject和getObjectType方法返回的类型设置的和类本身不一样,会发生什么?
根据Spring的机制,使用实现FactoryBean接口的Bean定义方式会生成两个Bean,一个是类本身的Bean,一个是getXxx方法返回的Bean,对应的测试代码如下
修改实现接口的Bean代码:
@Component
public class Teacher implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
修改对应的测试类:
public class Test3 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
// 获取的是getObject方法返回的对象
System.out.println(context.getBean("teacher"));
// 获取的是getObject方法所属类的对象
System.out.println(context.getBean("&teacher"));
}
}
三、Spring容器对应这两个类的实例化方式
由于Spring容器的初始化机制,我们首先需要扫描出符合要求的类,然后将这些类注册为一个BeanDefinition,对于BeanDefinition不太熟悉的小伙伴可以参考这篇文章:浅谈BeanDefinition、BeanDefinitionMap、RootBeanDefintion三者的关系,然后再将所有的非懒加载的Bean进行统一的实例化(即完成Bean的生命周期)。
对应的Spring源代码如下:
DefaultListableBeanFactory类的preInstantiateSingletons方法中
根据以上代码流程(实例化所有非懒加载Bean的第一步,遍历beanDefinitionNames集合,近似理解为BeanDefinitionMap,只是前者存的只有名字,后者是名字和BeanDefinition的键值对)我们分析:
- 遍历List集合中所有的名字
- 剔除是抽象类、不是单例、是懒加载的Bean
- 最终调用getBean方法(Spring的设计机制,getBean可以间接的理解为createBean)
通过以上描述我们不难发现,在容器的初始化阶段只会完成一个Bean的实例化,即类上带有@Component注解的类,也就是我们上面的Teacher类,那么
- 另一个User类在什么地方呢?
- 按照上面的分析,List集合中beanName是teacher,那么我们测试代码中获取Spring容器中名字叫teacher的Bean的时候,获取到的为什么是User类的Bean呢?
- 获取到Teacher类的时候又为什么需要在teacher字符串名字前面加&,才能获取到呢?
四、传入&teacher和teacher名字时,获取Bean对象流程
Spring源代码级别:
1.紧接上面的getBean方法,方法会调用doGetBean方法。根据Bean的名字去单例池中获取,如果获取到就调用对应的getObjectFromBeanInstance方法
由于Spring容器实例化已经完成,即容器中(单例池Map)是含有Teacher类对应的Bean
- 传入teacher:beanName是teacher,单例池中Teacher类对应的Bean
- 传入&teacher:beanName是teacher,单例池中Teacher类对应的Bean
- 仅针对本文的情况,该方法会调用内部的getObjectFromFactoryBean方法
后面有详细的说明
这是一个比较关键的方法
该方法的参数为:
- beanInstance(beanName对应的单例池中的Bean信息)
- name(外部调用get方法的字符串)
- beanName(对name字符串的处理,如果name是&XX,beanName就是XX,如果name是XX,则beanName相同)
- mbd(对应的beanDefinition信息)
传入teacher:调用getObjectFromFactoryBean方法进一步处理
传入&teacher:直接返回单例池中的对象
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
// name以&开头就返回true,否则false
if (BeanFactoryUtils.isFactoryDereference(name)) {
// 如果单例池中Bean类型是NullBean类型,直接返回
if (beanInstance instanceof NullBean) {
return beanInstance;
}
// 如果你的单例池中的Bean不是FactoryBean类型,就抛异常
// 因为前面已经判断,&开头的名字才能进入if,而我们希望通过&xx获取到的Bean对象一定是FactoryBean的实现类
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
}
}
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
// If it's a FactoryBean, we use it to create a bean instance, unless the
// caller actually wants a reference to the factory.
// 没有实现接口,返回false,在取反为true。或者是name以&开头为true
// 正常情况下的Bean,直接返回单例池中的对象
// 传入字符串为&teacher时,后半部分为true,直接返回单例池中的对象
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
// 既实现了factoryBean接口,传入的名字又带有&。此时就认为是去调用getObject方法
Object object = null;
if (mbd == null) {
// 去缓存中获取对应的对象
object = getCachedObjectForFactoryBean(beanName);
}
// 缓存中没有获取到,如果缓存中有就直接返回(这个缓存是用来缓存getObject方法中的对象的)
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
// BeanDefinitionMap中含有对应的名字的BeanDefinition
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//调用对应的方法:单例池中的实例,经过处理的beanName
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
- 调用方法内部的doGetObjectFromFactoryBean方法,完成真正的操作获取
传入teacher:先去缓存中获取,获取不到就调用doGetObjectFromFactoryBean方法获取
传入&teacher:上一步已经返回
- 在该方法内部完成getObject方法的调用
传入teacher:未打开安全管理器的情况下,调用factory接口的getObject方法,至此调用创建完成。
传入&teacher:上上一步已经返回
总结几个要点:
1. 实现FactoryBean接口的类,在创建Bean的时候会创建两个Bean,一个是类本身的Bean,一个是getObject方法对应的Bean。
2. 两个Bean创建的时间不同,类本身的Bean,是在Spring容器实例化的时候被扫描到,然后实例化Bean并添加到单例池中;但是getObject对应的Bean是在getBean的时候完成初始化,最终添加到通过getObject方法创建的Bean的缓存池中。
3. getObject方法对应的Bean与其他Bean的区别是,getObject方法对应的Bean没有完整的生命周期,因为完整生命周期的Bean是在Spring容器初始化的时候创建的,而getObject方法对应的Bean是在getBean的时候创建的,此时生命周期只有初始化后(AOP功能)。
4. 想要在Spring容器实例化阶段就去创建Bean也能够左到,只是需要将FactoryBean修改为SmartFactoryBean接口,然后将其isEagerInit方法的返回值修改为true即可,代码如下:
@Component
public class Teacher implements SmartFactoryBean {
@Override
public Object getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
@Override
public boolean isEagerInit() {
return true;
}
}
第四点原因:因为在初始化Spring容器的时候,他会去判断是否实现了SmartFactoryBean接口,以及对应的返回值是否为true,如果条件满足,就会立即去getBean,即立即去调用getObject方法,而不是等到你最终去调用的时候再去调用getObject方法
以上是关于浅谈使用实现FactoryBean接口的方式定义Bean的主要内容,如果未能解决你的问题,请参考以下文章
《Spring揭秘》---- 工厂方法与FactoryBean
Spring之BeanFactory和FactoryBean接口的区别