0.9、Spring 源碼學習 FactoryBean

前言

Spring 允許我們直接將一個 Java 類註冊爲 SpringBean ,同時還提供了另外一種方式,即 FactoryBean 方式,通過直接或者間接實現 FactoryBean 接口,我們同樣可以將一個 Java 類註冊爲 SpringBean。

FactoryBean 接口介紹

功能簡介

FactoryBean 支持泛型,通過實現 FactoryBean 接口,子類可以成爲 具體某一個 Java 類的工廠類,這樣我們在 Spring context 中獲取這個Java 類時,就可以直接通過 FactoryBean 來進行了。

具體來說,無須再將這個 Java 類聲明爲 SpringBean,而是將這個類對應的 FactoryBean 註冊爲 SpringBean。
這樣有什麼好處呢?原本你能將這個 Java 類註冊爲 普通的 SpringBean,實例化過程你是參與不了的,FactoryBean 則將這個 Java 類的實例化工作交給了你,就是可以進行自定義了。

當然,這裏還隱含一個問題,我們在日常使用中並沒有注意到 FactoryBean 的存在,即在調用過程中是無感的,Spring 是怎麼做到的呢?

下面我們從應用層面來看看 FactoryBean 的使用。

源碼路徑

org.springframework.beans.factory.FactoryBean

FactoryBean 源碼內容

FactoryBean 是一個支持泛形的接口,其內部有三個方法。
泛型的作用是指定該 FactoryBean 子類要具體實例化的 Java 類。
三個內部方法中,最主要的是 getObject(),子類將在這個方法中完成 泛型中指定的 Java 類 的實例化工作。

package org.springframework.beans.factory;
import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

	/**
	* 子類在內部手動實例化 T 類型的對象
	**/
    @Nullable
    T getObject() throws Exception;
	
	/**
	* 返回 T 的類型,比如 String.class
	**/
    @Nullable
    Class<?> getObjectType();
	
	/**
	*  是否是單例模式,默認爲 true
	**/
    default boolean isSingleton() {
        return true;
    }
}

FactoryBean 的使用

事實上,Spring 中還是存在一些“約定俗成”的東西的,FactoryBean 就算是其中一個。這主要體現在通過 FactoryBean 獲取 SpringBean 的過程中。
FactoryBean 是一個編程式規範,其具體實現不應該依賴於註解驅動的注入或者反射功能.。
FactoryBean 子類本身也是 SpringBean ,但是我們卻要通過它獲取另外一個 Java 對象,Spring 使用了一個命名區分的邏輯,當你使用FactoryBean的beanName來獲取時,Spring會調用這個名字對應Bean的 getObject() ,此時它是一個工廠類,當你需要獲取這個工廠類本身時,你可以在 beanName 前增加 “&” 符號,此時你獲取的就是 這個 FactoryBean子類本身的 SpringBean 了。

自定義普通類 SimpleClass

這個類將成爲 FactoryBean 子類的泛型限定類,也是 FactoryBean 子類 工廠負責產生對象的類型。

package com.bestcxx.test;

/**
 * @Title: 普通 bean
 * @Description: 自定義的一個普通 bean,正常而言我們會將該類註冊爲 SpringBean,但是這裏我們不會這樣做,而是測試 FactoryBean 的效果
 */
public class SimpleBean {
    /**自定義參數:時間戳*/
    private Long timestamp;

    public void printTimestamp(){
        System.out.println("timestamp="+this.timestamp);
    }
    public Long getTimestamp() {
        return timestamp;
    }
    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

}

自定義 FactoryBean 子類 MyFactoryBean

本類將完成三件事:

  • 實現 FactoryBean 接口,這裏子類我命名爲 MyFactoryBean,並指定泛型爲 SimpleBean
  • 覆蓋重寫 FactoryBean 接口吧三個方法 ,完成 SimpleBean 實例化邏輯
  • 使用註解方式將 MyFactoryBean 註冊爲 SpringBean
package com.bestcxx.test;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @Title: ClassA
 * @Description: ClassA
 */
@Component
public class MyFactoryBean implements FactoryBean<SimpleBean> {

    /**
     * 通過 MyFactoryBean.getObject() 方法給 SimpleBean 增加了一些額外的操作
     * @return
     * @throws Exception
     */
    @Override
    public SimpleBean getObject() throws Exception {
        SimpleBean simpleBean=new SimpleBean();
        simpleBean.setTimestamp(new Date().getTime());
        return simpleBean;
    }

    @Override
    public Class<?> getObjectType() {
        return SimpleBean.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

xml 配置文件

提供下 xml 配置文件,路徑爲 src/main/resources/test/applicationContext-test.xml
關鍵代碼就一句 < context:component-scan base-package=“com.bestcxx.test”/>

<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.bestcxx.test"/>
</beans>

調用時 beanName 中 “&” 是區分 MyFactoryBean 和 SimpleBean 的關鍵

爲了保證調用過程的無感,FactoryBean 的 beanName 默認表示 SimpleBean 對象,如果你想獲取 MyFactoryBean ,則需要在 beanName 前加“&”。
比如本文,獲取 SimpleBean對象 就用 “myFactoryBean”,如果獲取 MyFactoryBean 這個 SpringBean 本身,則用 “&myFactoryBean”。
這一塊的邏輯由Spring 負責,在 bean 初始化和獲取階段有體現。簡單來說就是 如果 SpringBean 爲 FactoryBean 類型,則判斷獲取 beanName 是否有 “&” 前綴,如果沒有則返回 getObject() 的結果值,否則則返回 MyFactoryBean 本身。
可能有人會問,“simpleBean” 還有用嗎?由於本文代碼沒有特別指定 beanName,所以從 Spring 角度來看, 只有 "myFactoryBean" 被註冊爲了 springBean的 beanName,“simpleBean”是不能用的。

在這裏插入圖片描述

測試方法

&myFactoryBean 用於測試 MyFactoryBean 這個 SpringBean的獲取
myFactoryBean 用於測試 SimpleBean 的獲取

測試代碼
package com.bestcxx.test;

import junit.framework.TestCase;
import org.junit.Test;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @Title: FactoryBean 測試
 * @Description: MyFactoryBean 的測試類
 */
public class MyFactoryBeanTest {

    /**
     * 測試 使用 FactoryBean的名字 和 &FactoryBean的名字 加載bean 的效果
     */
    @Test
    public void testMyBeanFactoryBean(){

        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:test/applicationContext-test.xml");
        TestCase.assertEquals("出錯了",applicationContext!=null,true);
		
		// &myFactoryBean 獲取 FactoryBean 類型對象
        Object object=applicationContext.getBean("&myFactoryBean");
        TestCase.assertEquals("出錯了2",object!=null,true);
        if(object instanceof SimpleBean){
            ((SimpleBean) object).printTimestamp();
        }else if(object instanceof FactoryBean){
            System.out.println("獲取到了 myFactoryBean 的實例 ");
        }

		// myFactoryBean 獲取 SimpleBean 類型對象
        Object object2=applicationContext.getBean("myFactoryBean");
        TestCase.assertEquals("出錯了3",object2!=null,true);
        if(object2 instanceof SimpleBean){
            ((SimpleBean) object2).printTimestamp();
        }else if(object2 instanceof FactoryBean){
            System.out.println("獲取到了 myFactoryBean 的實例 ");
        }
    }
}

測試結果
獲取到了 myFactoryBean 的實例 
timestamp=1576383881254
發佈了459 篇原創文章 · 獲贊 229 · 訪問量 187萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章