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