在學習Spring Core中IOC容器時,你確定會接觸到BeanFactory這個Spring中最基礎的IOC容器。這個應該是你們學習Spring源碼時最早接觸到的類了。Spring中還存在這一個FactoryBean類,二者拼寫上十分類似,而且使用頻率都十分得高。在一些Spring面試題,也會問你這二者有什麼區別。php
這裏先說結論:java
在學習Spring源碼和其餘開源項目的源碼的過程中,發現FactoryBean是一些框架在作集成Spring時常常會使用到的類,本文具體講述的也是FactoryBean的簡單實用和具體應用拓展。面試
Spring 中有兩種類型的Bean,一種是普通Bean,另外一種是工廠Bean 即 FactoryBean。spring
通常狀況下,Spring 經過反射機制利用bean的class屬性指定實現類來實例化bean 。在某些狀況下,實例化bean 過程比較複雜,若是按照傳統的方式,則須要在<bean>
中提供大量的配置信息,配置方式的靈活性是受限的, 這時採用編碼的方式可能會獲得一個簡單的方案。Spring 爲此提供了一個 org.Springframework.bean.factory.FactoryBean
的工廠類接口,用戶能夠經過實現該接口定製實例化bean的邏輯。(看後面的一些例子會理解更深入)sql
因此說,當配置一個<bean>
的過程很是複雜,建立過程當中涉及到不少其餘的bean 和複雜的邏輯,用xml配置比較困難,這時能夠考慮用FactoryBean。apache
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
複製代碼
<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> {
...
}
複製代碼
<?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 {
...
}
複製代碼
涉及bash
ProduceLocationmybatis
@Data
public class ProduceLocation {
private String locationName;
private double distanceKm;
private double pricePerPerKm;
}
複製代碼
Materialapp
@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