前言
在学习Spring Core中IOC容器时,你肯定会接触到BeanFactory这个Spring中最基础的IOC容器。这个应该是大家学习Spring源码时最先接触到的类了。Spring中还存在这一个FactoryBean类,两者拼写上十分相似,并且使用频率都十分得高。在一些Spring面试题,也会问你这两者有什么区别。
这里先说结论:
- BeanFactory:Spring中的IoC容器,所有Spring Bean 的Factory
- FactoryBean:一个Bean,一个不简单的Bean,一个能产生对象或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
在学习Spring源码和其他开源项目的源码的过程当中,发现FactoryBean是一些框架在做集成Spring时经常会使用到的类,本文具体讲述的也是FactoryBean的简单实用和具体应用拓展。
What is FactoryBean
Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。
一般情况下,Spring 通过反射机制利用bean的class属性指定实现类来实例化bean 。在某些情况下,实例化bean 过程比较复杂,如果按照传统的方式,则需要在<bean>
中提供大量的配置信息,配置方式的灵活性是受限的, 这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.Springframework.bean.factory.FactoryBean
的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。(看后面的一些例子会理解更深刻)
所以说,当配置一个<bean>
的过程非常复杂,创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。
接口定义
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
在一些开源框架上的使用
MyBatis-Spring # SqlSessionFactoryBean
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="***" />
<property name="configLocation" value="***"/>
<property name="mapperLocations" value="***"/>
</bean>
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
...
}
阿里开源的分布式服务框架 Dubbo # ReferenceBean<T>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->
<dubbo:application name="consumer-of-helloworld-app" />
<!-- 使用multicast广播注册中心暴露发现服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />
<!-- 生成远程服务代理,可以和本地bean一样使用demoService -->
<dubbo:reference id="demoService" interface="org.apache.dubbo.demo.DemoService" />
</beans>
<dubbo:reference
对应的Bean是com.alibaba.dubbo.config.spring.ReferenceBean
类
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
...
}
拓展实践
涉及
- ProduceLocation 生产地区
- Material 材料
- ProductFactoryBean 产品工厂bean
- Product 产品
- Boostrap 启动类
- test-config.xml 测试配置文件
ProduceLocation
@Data
public class ProduceLocation {
private String locationName;
private double distanceKm;
private double pricePerPerKm;
}
Material
@Data
public class Material {
private String name;
private double pricePerGram;
private double weight;
}
Product
@Data
@Builder
public class Product {
private Material material;
private ProduceLocation location;
private double price;
}
ProductFactoryBean
@Setter
@Getter
public class ProductFactoryBean implements FactoryBean<Product> {
private Material material;
private ProduceLocation produceLocation;
@Override
public Product getObject() throws Exception {
return Product.builder()
.location(produceLocation)
.material(material)
.price(cal(material, produceLocation))
.build();
}
private double cal(Material material, ProduceLocation produceLocation) {
return material.getPricePerGram() * material.getWeight()
+ produceLocation.getDistanceKm() * produceLocation.getPricePerPerKm();
}
@Override
public Class<?> getObjectType() {
return Product.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
test-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="produceLocation" class="base.ioc.FactoryBeanDemoSet.ProduceLocation">
<property name="locationName" value="杭州"/>
<property name="pricePerPerKm" value="151.01"/>
<property name="distanceKm" value="3.1"/>
</bean>
<bean id="material" class="base.ioc.FactoryBeanDemoSet.Material">
<property name="name" value="巧克力豆"/>
<property name="pricePerGram" value="100"/>
<property name="weight" value="50"/>
</bean>
<bean id="product" class="base.ioc.FactoryBeanDemoSet.ProductFactoryBean">
<property name="material" ref="material"/>
<property name="produceLocation" ref="produceLocation"/>
</bean>
</beans>
Boostrap
/**
* @author Richard_yyf
* @version 1.0 2019/9/21
*/
public class Bootstrap {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
Product product = (Product) context.getBean("product");
System.out.println(product.toString());
}
}
输出
Product(material=Material(name=巧克力豆, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName=杭州, distanceKm=3.1, pricePerPerKm=151.01), price=5468.131)
上述的配置当然也可以改用java config 的方式来做。
上述是一个简单业务的示例,你还可以通过FactoryBean来对一些开源工具API使用进行一些封装,比如对httpClient创建过程做了一些封装,例如超时时间、连接池大小、http代理等。
特性
给定一个id=mybean
的FactoryBean,getBean("mybean")
得到的就是这个FactoryBean创建的对象实例,而getBean("&mybean")
得到的确实FactoryBean自身对象。
根据上述的demo,运行如下代码
/**
* @author Richard_yyf
* @version 1.0 2019/9/21
*/
public class Bootstrap {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test-config.xml");
// Product product = (Product) context.getBean("product");
// System.out.println(product.toString());
FactoryBean<Product> factoryBean = (ProductFactoryBean) context.getBean("&product");
System.out.println(factoryBean.getObject().toString());
}
}
Output
Product(material=Material(name=巧克力豆, pricePerGram=100.0, weight=50.0), location=ProduceLocation(locationName=杭州, distanceKm=3.1, pricePerPerKm=151.01), price=5468.131)
对应源码
先直接锁定对应逻辑源码,
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
// Don't let calling code try to dereference the factory if the bean isn't a factory.
// BeanFactoryUtils.isFactoryDereference(name)方法判断name是否以&前缀
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), 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.
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
Object object = null;
if (mbd == null) {
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// Return bean instance from factory.
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// Caches object obtained from FactoryBean if it is a singleton.
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
-
BeanFactoryUtils.isFactoryDereference(name)
判断是否在获取FactoryBean
的引用// 不为空且以”&“开头 public static boolean isFactoryDereference(String name) { return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); } String FACTORY_BEAN_PREFIX = "&";
- 如果进来的
beanInstance
不是FactoryBean
,但调用者是用获取FactoryBean
的引用时,抛出BeanIsNotAFactoryException
异常 - 如果调用者是要获取
FactoryBean
的引用,且beanInstance
是FactoryBean
(前面有判断),则直接返回beanInstance
- 如果进来的
beanInstance
是普通bean,直接返回beanInstance
- 通过进来的
FactoryBean
来创建一个对应的bean
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。