1. 前言
在getBean方法中,即 bean 实例化完成之后,存在这样一段代码:
从方法名上可以得知它是用来通过实例创建对象的,既然上面已经创建了,这里为什么还会在创建一次呢?这就和FactoryBean
有关。
2. BeanFactory和FactoryBean
从定义上看,一个是bean工厂,一个是工厂bean,在 Spring 中,它们都是一个接口,都可以用来创建Bean实例,但各自的功能点却又存在不同。
2.1 BeanFactory
BeanFactory,他是Spring容器的一个顶层接口,里面定义了Spring IOC容器的一些规范,可以把它理解为一个容器。它最主要的方法就是getBean()
,该方法从容器中返回特定名称的Bean,BeanFactory 的功能通过其他的接口得到不断扩展。比如最常用的 ApplicationContext,也是其子类。
这个接口其实可以理解为 Spring 中用来创建和管理 Bean 实例的另外一个IOC容器,这个接口中除了包括BeanFactory 中的所有规范,还提供了其他的一些扩展功能,他的常用实现包括如下几个:
- FileSystemXmlApplicationContext:通过加载一个文件系统中的xml来初始化一个Spring容器;
- AnnotationConfigApplicationContext:通过加载一个JavaConfig配置类来初始化Spring容器;
- ClassPathXmlApplicationContext:通过加载classpath下的xml配置文件来初始化Spring容器。
如果使用 BeanFactory 的实现类来创建 Bean,那么就必须要遵守 Spring 中 Bean 的实例化流程。
2.2 FactoryBean
FactoryBean,实际上就是一个Java Bean,但是它是一个能生产对象的工厂Bean,BeanFactory创建对象的过程是非常复杂的,至少 xml 的方式肯定会让你头疼。
因此,Spring 提供了一个 FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 Bean 的逻辑。
3. 简单使用
FactoryBean是一个接口,如果要使用FactoryBean,需要自定义一个FactoryBean的实现类,如下:
@Component
public class MyFactoryBean implements FactoryBean<Student> {
@Override
public Student getObject() throws Exception {
// 该方法内可以编写较为复杂的创建逻辑...
Student student = new Student();
student.setName("factoryBean");
student.setAge(18);
return student;
}
@Override
public Class<?> getObjectType() {
return Student.class;
}
}
然后创建一个Student类:
import lombok.Data;
@Data
public class Student {
private String name;
private int age;
}
此时存在一个问题,我们的目的是想生成 Student 实例,但 MyFactoryBean 肯定会由 BeanFactory 来创建,而Student 通过FactoryBean 来创建,那么怎么区分呢?
Spring 使用了 “&” 前缀。如 beanName 为 myFactoryBean ,则 getBean("myFactoryBean")
获得的是 MyFactoryBean通过 getObject() 方法创建的 bean 实例,而 getBean("&myFactoryBean")
获得的是 MyFactoryBean 本身。
测试:
@Test
public void test5() {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student = applicationContext.getBean("myFactoryBean", Student.class);
System.out.println("myFactoryBean--->" + student);
MyFactoryBean factoryBean = applicationContext.getBean("&myFactoryBean", MyFactoryBean.class);
System.out.println("&myFactoryBean--->" + factoryBean);
}
输出:
myFactoryBean--->Student(name=factoryBean, age=18)
&myFactoryBean--->net.javatv.factorybean.MyFactoryBean@50eac852
4. 源码分析
对于 FactoryBean 的源码相对来说非常简单,进入AbstractBeanFactory#getObjectForBeanInstance()
。
1、首先会针对 beanName 进行判断,如果要获取到 FactoryBean 类本身,就必须加上”&”符号。
2、尝试从缓存中拿取,同 BeanFactory,它也有自己的缓存,但跟其他实例不是同一个缓存,对应的缓存是:factoryBeanObjectCache
。
3、如果缓存中没有,则进入 getObjectFromFactoryBean
获取对象。
进入该方法,其中包括了单例和多例的情况。
找到真正做事的方法doGetObjectFromFactoryBean
,可以看到就是调用的 FactoryBean 的 getObject()方法。
而此时getObject()
的实现类就是 MyFactoryBean 的实现:
5. 总结
FactoryBean在Spring中最为典型的一个应用就是用来创建AOP的代理对象。
我们知道 AOP 实际上是 Spring 在运行时创建了一个代理对象,也就是说这个对象,是我们在运行时创建的,而不是一开始就定义好的,这很符合工厂方法模式。更形象地说,AOP 代理对象通过 Java 的反射机制,在运行时创建了一个代理对象,在代理对象的目标方法中根据业务要求织入了相应的方法。这个对象在Spring中就是ProxyFactoryBean
。
所以,FactoryBean 为我们实例化Bean提供了一个更为灵活的方式,我们可以通过FactoryBean创建出更为复杂的Bean实例。
另外,Mybatis 中也使用到了 FactoryBean,在获取 Mapper 对象的时候,在getMapper方法底层就会通过JDK的动态代理去生成Mapper的默认实现,所以我们 Mapper 一般不需要写实现类,方法也可以直接调用。