说说 Spring 中的 FactoryBean

“BeanFactory 和 FactoryBean 的区别”是面试中常见被问到的 Spring 知识点,主要是考察候选人对 SpringIOC 的理解。

BeanFactory 不用多说,就是 SpringIOC 中 Bean 的工厂,常见的 XMLBeanFactory,ClassPathXmlApplicationContext,AnnotationConfigApplicationContext 等,都是 BeanFactory 的实现。BeanFactory 提供最常用的 getBean()方法用于获取 IOC 容器中的 Bean。

FactoryBean 也是个接口,提供 getObject(),getObjectType(),isSingleton()三个方法,在 SpringIOC 容器中实现了 FactoryBean 的 Bean 就像实现了 BeanPostProcessor,BeanFactoryPostProcessor 等接口的 Bean 一样,具有一些特殊能力(都属于 SpringIOC 扩展点的一部分)。

FactoryBean 的 getObject()方法用于返回自定义创建的 Bean,在 SpringIOC 中,通过 getBean()方法获取 FactoryBean 得到的并不是 FactoryBean 本身,而是 FactoryBean 中 getObject()方法返回的对象。而 FactoryBean 本身在 SpringIOC 容器中的名称是”&”+BeanName。

举个例子

有个 User 类:

1
2
3
public class User {
public String userName;
}

定义创建 User 的 FactoryBean:

1
2
3
4
5
6
7
8
9
10
11
12
@Component("userFactoryBean")
public class UserFactoryBean implements FactoryBean<User> {

public User getObject() throws Exception {
return new User();
}

public Class<?> getObjectType() {
return User.class;
}

}

通过 ApplicationContext 获取 Bean 验证效果:

1
2
3
4
5
6
@Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
System.out.println("userFactoryBean:" + context.getBean("userFactoryBean"));
System.out.println("&userFactoryBean:"+ context.getBean("&userFactoryBean"));
}

输出:

userFactoryBean: com.zhangzw.entity.User@3cc2931c
&userFactoryBean: com.zhangzw.bean.UserFactoryBean@20d28811

用途

读完上面的描述可能只是知道 FactoryBean 这个东西,还是不理解 Spring 为什么要 FactoryBean?FactoryBean 有什么用?

我们知道 Spring 中定义 Bean 有几种方式:通过 XML 定义,通过 @Component 注解,通过 @Bean 注解等。实现 FactoryBean 接口算是另一种方式,有时在创建 Bean 的过程中需要很多自定义的代码或需要其它的依赖等,我们就可以使用 FactoryBean 来定义 Bean。

比如,常用的 MyBatis 中的 SqlSessionFactoryBean 就是一个 FactoryBean,使用 MyBatis 的前提是需要创建 SqlSessionFactory 来连接数据库,但创建 SqlSessionFactory 需要很多其它依赖(解析 MyBatis 配置,加载和校验 SQLMapperXML 文件等),即可以理解为创建 SqlSessionFactory 是个很复杂的步骤。那么 MyBatis 为了我们在 Spring 中可以很简单的管理 SqlSessionFactory,在它的 mybatis-spring 包中就提供了 SqlSessionFactoryBean 类,专门用于创建 SqlSessionFactory(见:buildSqlSessionFactory()方法)。从而让开发者整合使用 Spring+MyBatis 时只需要定义 SqlSessionFactoryBean 并指定下 configLocation 就可以了。

其实在 Spring 生态中,FactoryBean 是广泛使用的。

附:Spring 官方文档中的介绍

You can implement the org.springframework.beans.factory.FactoryBean interface for objects that are themselves factories.

The FactoryBean interface is a point of pluggability into the Spring IoC container’s instantiation logic. If you have complex initialization code that is better expressed in Java as opposed to a (potentially) verbose amount of XML, you can create your own FactoryBean, write the complex initialization inside that class, and then plug your custom FactoryBean into the container.

The FactoryBean interface provides three methods:

  • Object getObject(): Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.
  • boolean isSingleton(): Returns true if this FactoryBean returns singletons or false otherwise.
  • Class getObjectType(): Returns the object type returned by the getObject() method or null if the type is not known in advance.

The FactoryBean concept and interface is used in a number of places within the Spring Framework. More than 50 implementations of the FactoryBean interface ship with Spring itself.

When you need to ask a container for an actual FactoryBean instance itself instead of the bean it produces, preface the bean’s id with the ampersand symbol (&) when calling the getBean() method of the ApplicationContext. So, for a given FactoryBean with an id of myBean, invoking getBean("myBean") on the container returns the product of the FactoryBean, whereas invoking getBean("&myBean") returns the FactoryBean instance itself.

参考文章

https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-factory-extension-factorybean