【Spring】一篇文章快速搞懂BeanFactory和FactoryBean的區別

目錄

一、BeanFactory

1.1 源碼

1.2 使用場景

二、FactoryBean

2.1 源碼

2.2 示例

2.2.1 方法一

2.2.2 方法二

2.3 FactoryBean的兩種用法

2.3.1 簡化xml配置,隱藏細節

2.3.2 返回不同Bean的實例

2.4 使用場景

三、BeanFactory和FactoryBean的區別以及共同點


一、BeanFactory

BeanFactory,以Factory結尾,表示它是一個工廠類(接口), 它負責生產和管理bean的一個工廠,我們可以通過它獲取工廠管理的對象。在Spring中,BeanFactoryIOC容器的核心接口,它的職責包括實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。它定義了getBean()containsBean()等管理Bean的通用方法。但BeanFactory只是個接口,並不是IOC容器的具體實現,但是Spring容器給出了很多種實現,如 :

  • DefaultListableBeanFactory
  • XmlBeanFactory
  • ApplicationContext

 

其中XmlBeanFactory就是常用的一個,該實現將以XML方式描述組成應用的對象及對象間的依賴關係。

 

1.1 源碼

public interface BeanFactory {
    /**
	用於區分factoryBean和bean,後面會講到
    /*String FACTORY_BEAN_PREFIX = "&";

    /**
     返回byName返回bean的實例
	*/
    Object getBean(String name) throws BeansException;

    <T> T getBean(String name, 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;


    /**
     * Return a provider for the specified bean, allowing for lazy on-demand retrieval
     * of instances, including availability and uniqueness options.
     * @param requiredType type the bean must match; can be an interface or superclass
     * @return a corresponding provider handle
     * @since 5.1
     * @see #getBeanProvider(ResolvableType)
     */

    <T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);

    /**
	 判斷工廠中是否包含給定名稱的bean定義,若有則返回true
	*/
    boolean containsBean(String name);

    /**
    判斷bean是否爲單例
	*/
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;

    /**
	判斷bean是否爲多例
	*/
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;

    /**
	檢查具有給定名稱的bean是否匹配指定的類型。
     */
    boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
    boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;

    /**
	返回給定名稱的bean的Class,如果沒有找到指定的bean實例,則排除*/
    @Nullable
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;

    /**
	返回給定bean名稱的所有別名 
	*/
    String[] getAliases(String name);
}

 

1.2 使用場景

  • 從Ioc容器中獲取Bean(byName or byType)
  • 檢索Ioc容器中是否包含指定的Bean
  • 判斷Bean是否爲單例

 

二、FactoryBean

使用XML配置spring容器的時候,Spring通過反射機制利用<bean>的class屬性指定實現類實例化Bean,在某些情況下,實例化Bean過程比較複雜,如果按照傳統的方式,則需要在<bean>中提供大量的配置信息。配置方式的靈活性是受限的,比如一個類大量依賴了其他的對象屬性,此時就算是使用自動裝配,不需要再顯式的寫出bean之間的依賴關係,但是其依賴的對象也需要將其裝配到spring容器中,也需要爲它所依賴的多有對象都創建bean標籤將他們注入,如果這個類依賴了上百個對象,那麼這個工作量無疑是非常大的。

Spring爲此提供了一個org.springframework.bean.factory.FactoryBean的工廠類接口,用戶可以通過實現該接口定製實例化Bean的邏輯。FactoryBean接口對於Spring框架來說佔用重要的地位,Spring自身就提供了70多個FactoryBean的實現。它們隱藏了實例化一些複雜Bean的細節,給上層應用帶來了便利。從Spring3.0開始,FactoryBean開始支持泛型,即接口聲明改爲FactoryBean<T>的形式。

Spring中共有兩種bean,一種爲普通bean,另一種則爲工廠bean

以Bean結尾,表示它是一個Bean,不同於普通Bean的是:它是實現了FactoryBean<T>接口的Bean,根據該Bean的ID從BeanFactory中獲取的實際上是FactoryBean的getObject()返回的對象,而不是FactoryBean本身,如果要獲取FactoryBean對象,請在id前面加一個&符號來獲取。

 

2.1 源碼

public interface FactoryBean<T> {
    //從工廠中獲取bean
    @Nullable
    T getObject() throws Exception;

    //獲取Bean工廠創建的對象的類型
    @Nullable
    Class<?> getObjectType();

    //Bean工廠創建的對象是否是單例模式
    default boolean isSingleton() {
        return true;
    }
}

從它定義的接口可以看出,FactoryBean表現的是一個工廠的職責。 即一個Bean A如果實現了FactoryBean接口,那麼A就變成了一個工廠,根據A的名稱獲取到的實際上是工廠調用getObject()返回的對象,而不是A本身,如果要獲取工廠A自身的實例,那麼需要在名稱前面加上'&'符號。

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

 

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

 

2.2 示例

我們現在要將下面這個TempDaoFactoryBean類交給工廠去創建管理

public class TempDaoFactoryBean {
    private String msg1;
    private String msg2;
    private String msg3;

    public void test() {
        System.out.println("FactoryBean");
    }
    public void setMsg1(String msg1) {
        this.msg1 = msg1;
    }
    public void setMsg2(String msg2) {
        this.msg2 = msg2;
    }
    public void setMsg3(String msg3) {
        this.msg3 = msg3;
    }
    public String getMsg1() {
        return msg1;
    }
    public String getMsg2() {
        return msg2;
    }
    public String getMsg3() {
        return msg3;
    }
}

 我們有兩種方法可以選擇:

方法一:通過spring的xml的方式對其進行配置.

方法二:定義一個CarProxy類,實現factoryBean接口.

 

2.2.1 方法一

如果使用傳統方式配置下面Car的<bean>時,Car的每個屬性分別對應一個<property>元素標籤,就算是使用自動裝配也要寫很多<bean>標籤,十分的麻煩

 

2.2.2 方法二

定義DaoFactoryBean實現FactoryBean接口

/**
 * FactoryBean由名字可以看出,是以bean結尾的,就說明這是一個bean,是由IOC容器管理的一個bean對象
 *
 * 如果你的類實現了FactoryBean
 * 那麼spring容器當中會存儲兩個對象:一個是getObject()方法返回的對象(TempDaoFactoryBean),還有一個就是當前對象(DaoFactoryBean)
 *
 * getObject()返回的對象(TempDaoFactoryBean)存儲在spring容器中給這個對象設置的beanName是當前類指定的對象,也就是     @Component("daoFactoryBean")  中的daoFactoryBean
 * 當前對象(DaoFactoryBean)在spring容器中設置的beanName是在@Component("")指定name的基礎上加一個“&”,這裏也就是&daoFactoryBean
 *  
 * ClassCastException類型轉換異常
 */
public class DaoFactoryBean implements FactoryBean {
     // DaoFactoryBean這個工廠bean管理的對象
    private String msg;

    // 使用setter方法將其注入
    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void testBean() {
        System.out.println("testBean");
    }

    @Override
    public Object getObject() throws Exception {    
        // 在FactoryBean內部創建對象實例
        TempDaoFactoryBean temp = new TempDaoFactoryBean();
        String[] msfArray = msg.split(",");
           temp.setMsg1(msfArray[0]);
        temp.setMsg2(msfArray[1]);
        temp.setMsg3(msfArray[2]);
        return temp;
    }

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

    /**
     * 是否是單例
     * @return
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

 使用xml將這個factoryBean裝配到spring容器中

<?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.xsd">
   
       
      <bean id="daoFactory" class="priv.cy.dao.DaoFactoryBean">
        <property name="msg" value="msg1,msg2,msg3"></property>
    </bean>

</beans>

 

測試類:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext
                = new AnnotationConfigApplicationContext(AppConfig.class);

        TempDaoFactoryBean tempDaoFactoryBean = (TempDaoFactoryBean) annotationConfigApplicationContext.getBean("daoFactory");
        System.out.println(tempDaoFactoryBean.getMsg1());
        System.out.println(tempDaoFactoryBean.getMsg2());
        System.out.println(tempDaoFactoryBean.getMsg3());
    }
}

 

執行結果:

 

因爲當我們getBean時,spring對實現了FactoryBean接口的類實現了特殊處理

當調用getBean("daoFactory")時,Spring通過反射機制發現DaoFactoryBean實現了FactoryBean的接口

這時Spring容器就調用接口方法中的getObject()方法返回。如果希望獲取CarFactoryBean的實例,

則需要在使用getBean(beanName)方法時在beanName前顯示的加上"&"前綴:如getBean("&car");  

 

2.3 FactoryBean的兩種用法

 

2.3.1 簡化xml配置,隱藏細節

如果一個類有很多的屬性,我們想通過Spring來對類中的屬性進行值的注入,勢必要在配置文件中書寫大量屬性配置,造成配置文件臃腫,那麼這時可以考慮使用FactoryBean來簡化配置

 

新建bean

public class Student {
    /** 姓名 */
    private String name;
    /** 年齡 */
    private int age;
    /** 班級名稱 */
    private String className;
    public Student() {
    }
    public Student(String name, int age, String className) {
        this.name = name;
        this.age = age;
        this.className = className;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + ", className='" + className + '\'' + '}';
    }
}

 

實現FactoryBean接口

public class StudentFactoryBean implements FactoryBean<Student> {
    private String studentInfo;
    @Override
    public Student getObject() throws Exception {
        if (this.studentInfo == null) {
            throw new IllegalArgumentException("'studentInfo' is required");
        }
        String[] splitStudentInfo = studentInfo.split(",");
        if (null == splitStudentInfo || splitStudentInfo.length != 3) {
            throw new IllegalArgumentException("'studentInfo' config error");
        }

        Student student = new Student();
        student.setName(splitStudentInfo[0]);
        student.setAge(Integer.valueOf(splitStudentInfo[1]));
        student.setClassName(splitStudentInfo[2]);
        return student;
    }
    @Override
    public Class<?> getObjectType() {
        return Student.class;
    }
    public void setStudentInfo(String studentInfo) {
        this.studentInfo = studentInfo;
    }
}

 

新建day03.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"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--注意:class是StudentFactoryBean而不是Student-->
    <bean id="student" class="com.lyc.cn.day03.StudentFactoryBean" p:studentInfo="張三,25,三年二班"/>
</beans>

 

測試類

public class MyTest {
    @Before
    public void before() {
        System.out.println("---測試開始---\n");
    }
    @After
    public void after() {
        System.out.println("\n---測試結束---");
    }
    @Test
    public void testStudentFactoryBean() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("day03.xml");
        System.out.println(applicationContext.getBean("student"));
        System.out.println(applicationContext.getBean("&student"));
    }
}

 

運行

---測試開始---

Student{name='張三', age=25, className='三年二班'}

org.springframework.beans.factory_bean.StudentFactoryBean@1ae369b7

---測試結束---

 

這樣我們就實現了通過BeanFactory接口達到了簡化配置文件的作用。另外大家也可以發現getBean(“student”)返回的Student類的實例;而getBean("&student")返回的是StudentFactoryBean實例,即工廠bean其本身。

 

2.3.2 返回不同Bean的實例

既然FactoryBean是一種工廠bean,那麼我們就可以根據需要的類型,返回不同的bean的實例,通過代碼簡單說明一下

 

新建bean

public interface Animal {
    void sayHello();
}

public class Cat implements Animal {
    @Override
    public void sayHello() {
        System.out.println("hello, 喵喵喵...");
    }
}

public class Dog implements Animal {
    @Override
    public void sayHello() {
        System.out.println("hello, 汪汪汪...");
    }
}

創建了一個Animal接口極其兩個實現類Cat和Dog,並進行簡單輸出,那麼如何通過FactoryBean來通過配置返回不同的Animal實例呢

 

新建AnimalFactoryBean

public class AnimalFactoryBean implements FactoryBean<Animal> {
    private String animal;

    @Override
    public Animal getObject() throws Exception {
        if (null == animal) {
            throw new IllegalArgumentException("'animal' is required");
        }
        if ("cat".equals(animal)) {
            return new Cat();
        } else if ("dog".equals(animal)) {
            return new Dog();
        } else {
            throw new IllegalArgumentException("animal type error");
        }
    }

    @Override
    public Class<?> getObjectType() {
        if (null == animal) {
            throw new IllegalArgumentException("'animal' is required");
        }
        if ("cat".equals(animal)) {
            return Cat.class;
        } else if ("dog".equals(animal)) {
            return Dog.class;
        } else {
            throw new IllegalArgumentException("animal type error");
        }
    }
    public void setAnimal(String animal) {
        this.animal = animal;
    }
}

 

修改day03.xml配置文件,增加bean

<bean id="animal" class="com.lyc.cn.day03.AnimalFactoryBean" p:animal="cat"/>

 

在MyTest中添加測試用例

@Test
public void testAnimalFactoryBean() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("day03.xml");
    Animal animal = applicationContext.getBean("animal", Animal.class);
    animal.sayHello();
}

 

運行

---測試開始---

hello, 喵喵喵...

---測試結束---

可以看到,配置文件裏我們將animal配置成了cat,那麼返回的就是cat的實例,也是簡單工廠的一個實現

 

 

2.4 使用場景

說了這麼多,爲什麼要有FactoryBean這個東西呢,有什麼具體的作用嗎?

 

FactoryBean在Spring中最爲典型的一個應用就是用來創建AOP的代理對象。

我們知道AOP實際上是Spring在運行時創建了一個代理對象,也就是說這個對象,是我們在運行時創建的,而不是一開始就定義好的,這很符合工廠方法模式。更形象地說,AOP代理對象通過Java的反射機制,在運行時創建了一個代理對象,在代理對象的目標方法中根據業務要求織入了相應的方法。這個對象在Spring中就是——ProxyFactoryBean。

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

 

還有比如我們spring需要整合mybatis,在沒有spring-mybatis的情況下(spring-mybatis會幫助你將MyBatis 代碼無縫地整合到Spring ),我們需要將mybatis核心類SqlSessionFactory注入到spring容器,那麼思考使用最常用的兩種方式:

  1. 註解,可是mybatis是個我們引用的獨立的項目.與我們自己的項目源碼無關,我們無法去修改它的源碼,在它的源碼上添加註解,所以不能使用註解的方法
  2. xml,sqlSessionFacory需要注入許多的依賴,如果使用XML來配置,需要我們寫大量的配置標籤,非常不方便維護。

所以可以選擇一個代理類去處理sqlSessionFacory,也就是我們在整合spring+mybatis時使用的SqlSessionFactoryBean,這個類是由mybatis提供的用來方便我們快速配置mybatis的factoryBean,通過這個類把很多繁瑣的配置代碼封裝了起來,類似於裝飾者模式,SqlSessionFactoryBean裏面管理了sqlSessionFacory並且對他進行相關配置設置操作,我們只需要將SqlSessionFactoryBean注入到spring容器中,並在在xml向這個factoryBean傳入一些簡單的配置信息,SqlSessionFactoryBean就會幫我們自動配置好sqlSessionFacory,很多複雜的配置都幫我們填充好了,然後我們就可以通過SqlSessionFactoryBean獲取已經配置完成的sqlSessionFacory。

 

三、BeanFactoryFactoryBean的區別以及共同點

共同點:都是接口

區別:

  • BeanFactory 以Factory結尾,表示它是一個工廠類,用於管理Bean的一個工廠。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)來進行管理的。該接口是IoC容器的頂級接口,是IoC容器的最基礎實現,也是訪問Spring容器的根接口,負責對bean的創建,訪問等工作
  • 對FactoryBean而言,以Bean結尾,說明這是一個交給容器去管理的bean。這個Bean不是簡單的Bean,而是一個能生產或者修飾對象生成的工廠Bean,它的實現與設計模式中的工廠模式和修飾器模式類似。

參考資料:https://blog.csdn.net/lyc_liyanchao/article/details/82424122

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章