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

前言

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

這裏先說結論:面試

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

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

What is FactoryBean

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();
}

在一些開源框架上的使用

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.ReferenceBeanmybatis

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {
    ...
}

拓展實踐

涉及app

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

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;
    }
  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
相關文章
相關標籤/搜索