Spring源碼系列 - FactoryBean 應用拓展(附源碼解析)

前言

在學習Spring Core中IOC容器時,你確定會接觸到BeanFactory這個Spring中最基礎的IOC容器。這個應該是你們學習Spring源碼時最早接觸到的類了。Spring中還存在這一個FactoryBean類,二者拼寫上十分類似,而且使用頻率都十分得高。在一些Spring面試題,也會問你這二者有什麼區別。php

這裏先說結論:java

  • BeanFactory:Spring中的IoC容器,全部Spring Bean 的Factory
  • FactoryBean:一個Bean,一個不簡單的Bean,一個能產生對象或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式相似

在學習Spring源碼和其餘開源項目的源碼的過程中,發現FactoryBean是一些框架在作集成Spring時常常會使用到的類,本文具體講述的也是FactoryBean的簡單實用和具體應用拓展。面試

What is FactoryBean

Spring 中有兩種類型的Bean,一種是普通Bean,另外一種是工廠Bean 即 FactoryBean。spring

通常狀況下,Spring 經過反射機制利用bean的class屬性指定實現類來實例化bean 。在某些狀況下,實例化bean 過程比較複雜,若是按照傳統的方式,則須要在<bean>中提供大量的配置信息,配置方式的靈活性是受限的, 這時採用編碼的方式可能會獲得一個簡單的方案。Spring 爲此提供了一個 org.Springframework.bean.factory.FactoryBean的工廠類接口,用戶能夠經過實現該接口定製實例化bean的邏輯。(看後面的一些例子會理解更深入)sql

因此說,當配置一個<bean>的過程很是複雜,建立過程當中涉及到不少其餘的bean 和複雜的邏輯,用xml配置比較困難,這時能夠考慮用FactoryBeanapache

接口定義

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

<?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

  • ProduceLocation 生產地區
  • Material 材料
  • ProductFactoryBean 產品工廠bean
  • Product 產品
  • Boostrap 啓動類
  • test-config.xml 測試配置文件

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;
    }
複製代碼
  1. BeanFactoryUtils.isFactoryDereference(name) 判斷是否在獲取FactoryBean的引用

    // 不爲空且以」&「開頭
    	public static boolean isFactoryDereference(String name) {
    		return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
    	}
    	String FACTORY_BEAN_PREFIX = "&";
    複製代碼
  2. 若是進來的beanInstance不是FactoryBean,但調用者是用獲取FactoryBean的引用時,拋出BeanIsNotAFactoryException異常

  3. 若是調用者是要獲取FactoryBean的引用,且beanInstanceFactoryBean(前面有判斷),則直接返回beanInstance

  4. 若是進來的beanInstance是普通bean,直接返回beanInstance

  5. 經過進來的FactoryBean 來建立一個對應的bean

相關文章
相關標籤/搜索