注:《Spring5源码分析》汇总可参考:Spring5源码分析(002)——博客汇总
1、FactoryBean 的用法
一般情况下,Spring 是通过反射机制利用 bean 的 class 属性指定的实现类来实例化 bean 的。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在 bean 中提供大量的配置信息,配置方式的灵活性是受限的,这时候采用编码的方式可能会得到一个简单的方案(当然如果有大量这样的处理的话,灵活性也同样有限,当作是一种补充就行)。Spring 为此提供了一个 org.springframework.beans.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 bean 的逻辑。
FactoryBean 接口对于 Spring 框架来说占有重要的地位, Spring 本身就提供了70多个 FactoryBean 实现类。他们隐藏了实例化一些复杂 bean 的细节,给上层应用带来了便利。从 Spring3.0 开始, FactoryBean 开始支持泛型,即接口声明改为 FactoryBean<T> 的形式:
public interface FactoryBean<T> { T getObject() throws Exception; Class<?> getObjectType(); default boolean isSingleton() { return true; } }
该接口中定义了3个方法:
- T getObject()
- 返回由 FactoryBean 创建的 bean 实例,如果 isSingleton() 返回 true ,则该实例会放到 Spring 容器的单例缓存池中。
- Class<?> getObjectType()
- 返回 FactoryBean 创建的 bean 类型
- boolean isSingleton()
- 返回由 FactoryBean 创建的 bean 实例的作用域是 singleton 还是 prototype ,默认是单例 singleton ,即返回 true 。
当配置文件中 <bean> 的 class 属性配置的实现类是 FactoryBean 时,通过 getBean() 方法返回的不是 FactoryBean 本身,而是 FactoryBean#getObject() 方法所返回的对象,相当于代理了 getBean() 方法。例如,如果使用传统方式配置下面的 Car 的 <bean> 时,Car 的每个属性则需要分别对应一个 <property> 元素标签:
public class Car { private int maxSpeed; private String brand; private double price; // setter and getter ... }
如果使用的是 FactoryBean 的方式来实现,则可以灵活配置,例如下面通过逗号分隔符的方式一次性地为 Car 的所有属性指定配置值:
public class CarFactoryBean implements FactoryBean<Car> { private String carInfo; @Override public Car getObject() throws Exception { Car car = new Car(); String[] infos = carInfo.split(","); car.setBrand(infos[0]); car.setMaxSpeed(Integer.valueOf(infos[1])); car.setPrice(Double.valueOf(infos[2])); return car; } @Override public Class<Car> getObjectType() { return Car.class; } @Override public boolean isSingleton() { return false; } public String getCarInfo() { return this.carInfo; } // 接收逗号分隔符设置属性信息 public void setCarInfo(String carInfo) { this.carInfo = carInfo; } }
有了这个 CarFactoryBean 之后,就可以在配置文件中使用下面这种自定义的配置方式了:
<bean id="carBean" class="cn.wpbxin.bean.CarFactoryBean">
<property name="carInfo" value="超跑,400,2000000" />
</bean>
当调用 getBean("carBean") 时,Spring 通过反射机制发现 CarFactoryBean 实现了 FactoryBean 的接口,这时 Spring 容器就调用接口方法 CarFactoryBean#getObject() 方法返回。如果希望获取 CarFactoryBean 实例,则需要在使用 getBean(beanName) 时在 beanName 签加上 “&” 前缀,例如 getBean("&carBean"),表示需要获取 FactoryBean 实例本身,也就是 CarFactoryBean 本身。
2、参考
- [1]1.8.3. Customizing Instantiation Logic with a FactoryBean https://docs.spring.io/spring-framework/docs/5.2.3.RELEASE/spring-framework-reference/core.html#beans-factory-extension-factorybean
- [2]Spring源码深度解析(第2版),郝佳,P92-P93
- [3]相关注释和案例可参考笔者 github 链接:https://github.com/wpbxin/spring-framework