Spring中的BeanFactory與FactoryBean看這一篇就夠了

宜春發表於2020-11-03

前言
理解FactoryBean是非常非常有必要的,因為在Spring中FactoryBean最為典型的一個應用就是用來建立AOP的代理物件,不僅如此,而且對理解Mybatis核心原始碼也非常有幫助!如果甘願crud,做個快樂的碼農,那我就哦豁豁豁豁豁豁豁豁豁豁豁豁豁豁......
@

BeanFactory和FactoryBean同樣都是spring的介面,名字看起來很相似,但是我覺得要混淆還是很困難的!儘管Spring揭祕一書的作者都喜歡寫上這一句。

請不要混淆BeanFactory 和 FactoryBean。

1、BeanFactory

BeanFactory,以Factory結尾,表示它是一個工廠(介面), 它負責生產和管理bean的一個工廠。在Spring中,BeanFactory是工廠的頂層介面,也是IOC容器的核心介面,因此BeanFactory中定義了管理Bean的通用方法,如 getBeancontainsBean 等,它的職責包括:例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。BeanFactory只是個介面,並不是IOC容器的具體實現,所以Spring容器給出了很多種實現,如 DefaultListableBeanFactoryXmlBeanFactoryApplicationContext等,其中XmlBeanFactory就是常用的一個,該實現將以XML方式描述組成應用的物件及物件間的依賴關係。

1.1 BeanFactory 原始碼

public interface BeanFactory {
	//對FactoryBean的轉義定義,因為如果使用bean的名字檢索FactoryBean得到的物件是工廠生成的物件,
	//如果需要得到工廠本身,需要轉義
	String FACTORY_BEAN_PREFIX = "&";

	//根據bean的名字,獲取在IOC容器中得到bean例項
	Object getBean(String name) throws BeansException;

	//根據bean的名字和Class型別來得到bean例項,增加了型別安全驗證機制。
	<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;

	Object getBean(String name, Object... args) throws BeansException;

	<T> T getBean(Class<T> requiredType) throws BeansException;

	<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;

	//提供對bean的檢索,看看是否在IOC容器有這個名字的bean
	boolean containsBean(String name);

	//根據bean名字得到bean例項,並同時判斷這個bean是不是單例
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

	boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;

	boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

	//得到bean例項的Class型別
	@Nullable
	Class<?> getType(String name) throws NoSuchBeanDefinitionException;

	//得到bean的別名,如果根據別名檢索,那麼其原名也會被檢索出來
	String[] getAliases(String name);
}

1.2、BeanFactory 使用場景

1、從Ioc容器中獲取Bean(byName or byType)
2、檢索Ioc容器中是否包含指定的Bean
3、判斷Bean是否為單例

2、FactoryBean

首先FactoryBean是一個Bean,但又不僅僅是一個Bean,這樣聽起來矛盾,但為啥又這樣說呢?其實在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。但對FactoryBean而言,這個FactoryBean不是簡單的Bean,而是一個能生產或者修飾物件生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似

2.1、為什麼會有FactoryBean?

一般情況下,Spring通過反射機制利用的class屬性指定實現類例項化Bean。至於為什麼會有FactoryBean?原因有兩個:

1、
在某些情況下,例項化Bean過程比較複雜,如果按照傳統的方式,則需要在中提供大量的配置資訊。配置方式的靈活性是受限的,這時採用編碼的方式可能會得到一個簡單的方案。Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類介面,使用者可以通過實現該介面定製例項化Bean的邏輯。FactoryBean介面對於Spring框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了例項化一些複雜Bean的細節,給上層應用帶來了便利。

2、
由於第三方庫不能直接註冊到spring容器,於是可以實現org.springframework.bean.factory.FactoryBean介面,然後給出自己物件的例項化程式碼即可。

2.2 、FactoryBean 原始碼

public interface FactoryBean<T> {
	//從工廠中獲取bean【這個方法是FactoryBean的核心】
	@Nullable
	T getObject() throws Exception;
	
	//獲取Bean工廠建立的物件的型別【注意這個方法主要作用是:該方法返回的型別是在ioc容器中getbean所匹配的型別】
	@Nullable
	Class<?> getObjectType();
	
	//Bean工廠建立的物件是否是單例模式
	default boolean isSingleton() {
		return true;
	}
}

方法介紹:
1、
getobject ()方法會返回該FactoryBean “生產” 的物件例項,我們需要實現該方法以給出自己的物件例項化邏輯;
2、
getobjectTYype ()方法僅返回getobject ()方法所返回的物件的型別,如果預先無法確定,則返回null; 特別注意這個方法主要作用是:該方法返回的型別是在ioc容器中getbean所匹配的型別,也就是說ioc中有很多型別的bean,要找到這個bean就是通過getobjectTYype ()方法的返回值型別!好吧,我舉個例子

public class XXX implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return new YYY;
    }       
    @Override
    public Class<?> getObjectType() {  //注意這個方法主要作用是:該方法返回的型別是在ioc容器中getbean所匹配的型別
        return AAA.class;
    }
}

那麼要想在ioc中找到XXX這個類的bean(實際上是YYY) ,在getbean的時候寫法如下

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = 
                new AnnotationConfigApplicationContext(Appconfig.class);
                
        annotationConfigApplicationContext.getBean( AAA.class ); // 【注意這裡是AAA.class】

    }
}

3、
isSingleton ()方法返回結果用於表明,工廠方法(getobject ())所“生產”的物件是否要以singleton形式存在於容器中。如果以singleton形式存在,則返回true,否則返回false;

FactoryBean表現的是一個工廠的職責。 即一個Bean A如果實現了FactoryBean介面,那麼A就變成了一個工廠,根據A的名稱獲取到的實際上是工廠呼叫getObject()返回的物件,而不是A本身,如果要獲取工廠A自身的例項,那麼需要在名稱前面加上'&'符號。 通俗點表達就是

getObject(' name ')返回工廠中的例項
getObject(' &name ')返回工廠本身的例項

通常情況下,bean 無須自己實現工廠模式,Spring 容器擔任了工廠的 角色;但少數情況下,容器中的 bean 本身就是工廠,作用是產生其他 bean 例項。由工廠 bean 產生的其他 bean 例項,不再由 Spring 容器產生,因此與普通 bean 的配置不同,不再需要提供 class 元素。

2.3 、FactoryBean程式碼示例

1、建立一個Appconfig類,掃描com.yichun下的所有子包

@Configuration
@ComponentScan("com.yichun")
public class Appconfig {
}

2、建立一個StudentBean類並實現FactoryBean,並重寫其兩個方法

@Component("studentBean")
public class StudentBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        return new TeacherBean();
    }
   
    @Override
    public Class<?> getObjectType() {  //注意這個方法主要作用是:該方法返回的型別是在ioc容器中getbean所匹配的型別
        return StudentBean.class;
    }
    //一個學生學習方法
    public void study(){
        System.out.println("學生學習。。。");
    }
}

3、再建立一個TeacherBean類

public class TeacherBean {
    public void teacher(){
        System.out.println("老師教書。。。。");
    }
}

4、測試StudentBean型別

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);
        StudentBean studentBean = (StudentBean)annotationConfigApplicationContext.getBean("studentBean");
        studentBean.study();
    }
}

在這裡插入圖片描述
加上“&”符號

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);
        //加上了“&”符號
        StudentBean studentBean = (StudentBean)annotationConfigApplicationContext.getBean("&studentBean");
        studentBean.study();
    }
}

在這裡插入圖片描述
執行成功

5、測試一下teacherBean型別

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(Appconfig.class);    
        TeacherBean teacherBean = (TeacherBean) annotationConfigApplicationContext.getBean("studentBean");       
        teacherBean.teacher();
    }
}

執行成功
在這裡插入圖片描述

2.4 FactoryBean使用場景

使用場景一:
FactoryBean在Spring中最為典型的一個應用就是用來建立AOP的代理物件。
我們知道AOP實際上是Spring在執行時建立了一個代理物件,也就是說這個物件,是我們在執行時建立的,而不是一開始就定義好的,這很符合工廠方法模式。更形象地說,AOP代理物件通過Java的反射機制,在執行時建立了一個代理物件,在代理物件的目標方法中根據業務要求織入了相應的方法。這個物件在Spring中就是——ProxyFactoryBean

所以,FactoryBean為我們例項化Bean提供了一個更為靈活的方式,我們可以通過FactoryBean建立出更為複雜的Bean例項。

當然在spring中,Spring容器內部許多地方了使用FactoryBean。下面是一些比較常見的FactoryBean實現:

JndiobjectFactoryBean
LocalSessionFactoryBean
SqlMapClientFactoryBean
ProxyFactoryBean
TransactionProxyFactoryBean

使用場景二:
Mybatis中的SqlSessionFactoryBean

<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="trade" />
        <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
    </bean>

package org.mybatis.spring;

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
        ......
}

另外提一下,阿里開源的分散式服務框架 Dubbo中的Consumer 也使用到了FactoryBean,

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    ">
     
    <!-- 當前應用資訊配置 -->
    <dubbo:application name="demo-consumer" />
   
   <!-- 暴露服務協議配置 -->
    <dubbo:protocol name="dubbo" port="20813" />    

    <!-- 暴露服務配置 -->
    <dubbo:reference id="demoService" interface="com.alibaba.dubbo.config.spring.api.DemoService"  />
     
</beans>

<dubbo:reference 對應的Bean是com.alibaba.dubbo.config.spring.ReferenceBean 類。

使用場景三:
Hibernate中的SessionFactoryBean。這裡就不再概述。

最後本文難免會有不正之處,歡迎指正評判!歡迎指正評判!歡迎指正評判!!!

參考:
《Spring揭祕》王福強
https://zhuanlan.zhihu.com/p/87382038
https://www.cnblogs.com/aspirant/p/9082858.html

相關文章