1. 程式人生 > >讀《Spring-技術內幕》-第二章:IoC容器的實現-2

讀《Spring-技術內幕》-第二章:IoC容器的實現-2

IoC容器系列的設計與實現

概要

       部落格介紹了BeanFactory這個容器的基本介面,如getBean(String name),containsBean(String name)等等,但是這些介面並沒有具體的實現,因此給出了XmlBeanFactory容器的實現過程,還用程式設計的方式使用了DefaultListableBeanFactory這個容器,成功得獲得了在BeanDefinition中定義的一個Bean.通過程式設計式的使用,讓我們明白IoC容器中的關鍵的類之間的相互依賴關係,如Resource,BeanDefinitionReader等。

//Spring中BeanDefinition都要封裝成Resource的格式才能進行處理,因為beans.xml在類路徑下,所以用ClassPathResource
ClassPathResource res =new ClassPathResource("beans.xml"); DefalutListableBeanFactory factory =new DefaultListableBeanFactory(); //對XML檔案的處理其實是通過XmlBeanDefinitionReader來處理的(詳見BeanFactory容器的設計原理),然後可以理解需要將得到的Bean定義資訊傳入factory工廠中,讓它生成物件,所以需要傳入一個factory物件。 XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); //讓res進行載入
read.loadBeanDefinitions(res); //其實這樣就完成了容器初始化的過程,後面就可以通過getBean(String name )得到相應的Bean了。大家可以試試~~

BeanFactory的應用場景

       正如上一篇所提到的,BeanFactory提供的是最基本的IoC容器的功能。BeanFactory介面定義了IoC容器最基本的形式。很顯然,Spring並沒有給出容器的具體實現。下面來看看Spring是怎樣定義IoC容器的基本介面的。
       首先最基本的是設計了getBean方法,這個方法是使用IoC容器API的主要方法。通過這個方法,可以取得這個容器管理的Bean(通過指定名字來索引),取得了Bean之後,就可以應用在程式之中。
BeanFactory介面:

public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
//很方便的通過各種不同方法獲取容器中的Bean
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//判斷容器中是否包含該名字的Bean
boolean containsBean(String name);
/*判斷Bean的作用域:
prototype :該作用域將單一 bean 的定義限制在任意數量的物件例項
singleton : 該作用域將 bean 的定義的限制在每一個 Spring IoC 容器中的一個單一例項(預設)。
*/
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
//判斷該名字對應的Bean的Class型別是否是特定的Class型別。這個Class型別可以由使用者來指定
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//得到該Bean的Class型別
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//查詢該Bean的所有別名。這些別名都是在BeanDefinition中定義的
String[] getAliases(String name);

BeanFactory容器的設計原理

       BeanDefinition提供了使用IoC容器的規範,在這個基礎上,Spring還提供了符合這個IoC容器介面的一系列的容器的實現讓我們使用。以XmlBeanFactory的實現為例:

       可以看到,XmlBeanFactory不像之前的ApplicationContext,後者繼承了各式各樣的介面,有著豐富的功能(上一篇有說明),XmlBeanFactory只提供了最基本的IoC容器的功能。我們可以認為:直接的BeanFactory實現是IoC容器的基本形式,比如這個XmlBeanFactory.而各種ApplicationContext的實現則是IoC容器的高階表現形式。 下面,我們就從XmlBeanFactory的實現入手分析,看看一個基本的IoC容器是怎樣實現的。
       XmlBeanFactory繼承自DefaultListableBeanFactory這個類,後者非常重要,是我們經常要用到的一個IoC容器的實現,在設計ApplicationContext的時候也有用到它。DefaultListableBeanFactory實際上已經包含了基本IoC容器所具有的重要功能。在Spring中實際上是把DefaultListableBeanFactory作為一個預設的功能完整的IoC容器來使用的。而XmlBeanFactory繼承了它之後,又增加了新的功能:它是一個可以讀取以XML檔案定義BeanDefinition的IoC容器。XmlBeanFactory又是怎樣實現讀取XML檔案的呢?
1. 其實,對這些XML檔案的處理並不是由XmlBeanFactory直接完成的,而是它裡面初始化的一個XmlBeanDefinitionReader物件。所以實際上是由XmlBeanDefinitionReader來完成XML檔案處理的。
2. 在構建XmlBeanFactory這個IoC容器時,需要指定BeanDefinition的資訊來源,而且這個來源需要封裝成Spring中的Resource類來給出。(Resource是Spring用來封裝I/O操作的類)。比如BeanDefinition是以XML檔案的形式,那麼可以使用像:ClassPath-Resource res =new ClassPathResource(“beans.xml”) 這樣具體的ClassPathResource來構造Resource。
3. 將前面構造的Resource作為構造引數傳遞給XmlBeanFactory建構函式。這樣IoC容器就可以定位到這個BeanDefinition,從而對Bean完成容器的初始化和依賴注入過程。

 public class XmlBeanFactory extends DefaultListableBeanFactory {
//初始化XmlBeanDefinitionReader物件
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
//從BeanDefinition資原始檔中載入資訊,loadBeanDefinitions也是IoC容器初始化的重要組成部分
        this.reader.loadBeanDefinitions(resource);
    }

       可以看到,XmlBeanFactory繼承了DefaultListableBeanFactory,後者的重要性不多說~(已經寫了好幾遍了)參考XmlBeanFactory的實現過程,我們用程式設計的方式使用DefaultListableBeanFactory,因為這個程式設計式使用DefaultListableBeanFactory的過程,可以讓我們明白IoC容器中的關鍵的類之間的相互依賴關係,如Resource,BeanDefinitionReader等等。

ClassPathResource res =new ClassPathResource("beans.xml");
DefaultListableBeanFactory factory =new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinition(res);

//假如BeanDefinition中有一個name為helloWorld的Bean
HelloWorld hw5 = (HelloWorld) factory.getBean("helloWorld");
        System.out.println("5=" + hw5.getMessage());

beans.xml中的BeanDefinition定義:

<bean id="helloWorld" class="com.lumingfeng.springStudy.helloSpring.HelloWorld" scope="singleton" init-method="init" destroy-method="destroy" >
        <property name="message" value="hello,spring">
        </property>
    </bean>

HelloWorld Bean類:

package com.lumingfeng.springStudy.helloSpring;

public class HelloWorld {
    private String message;
    private String address;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public void init(){
        System.out.println("被初始化");
        this.address="123";
    }

    public String getAddress(){
        return address;
    }

    public void destroy(){
        System.out.println("被銷燬");
        this.address="456";
    }

}

輸出的結果:

5=hello,spring

       這樣,我們就可以通過factory物件來使用DefaultListableBeanFactory這個IoC容器了。這個過程和我們上面提到的XmlBeanFactory讀取XML檔案的步驟是基本一致的:
1. 建立IoC配置檔案的抽象資源,這個抽象資源包含了BeanDefinition的定義資訊(ClassPathResource res =new ClassPathResource(“beans.xml”);)
2. 建立一個BeanFactory,這裡使用了DefaultListableBeanFactory(DefaultListableBeanFactory factory =new DefaultListableBeanFactory();)
3. 建立一個載入BeanDefiniton的讀取器–BeanDefinitionReader物件,這裡使用XmlBeandefinitionReader來載入XML檔案形式的(XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);)BeanDefinition,然後通過一個回撥配置給BeanFactory。
4. 從定義的資原始檔位置讀取配置資訊,具體這個解析過程由這裡使用XmlBeandefinitionReader來完成(reader.loadBeanDefinition(res);)
        完成整個載入和註冊Bean定義之後,需要的IoC容器就建立起來了。這個時候就可以直接使用IoC容器了

ApplicationContext的應用場景

       上一節中我們瞭解了IoC容器建立的基本步驟,可以很方便的通過程式設計的方式來手動控制這些配置和容器的建立過程。但是在Spring中系統為我們提供了許多已經定義好的容器實現,如ApplicationContext.


       如圖,可以看到ApplicationContext在BeanFactory的基礎上添加了附加功能
1. 支援不同的資訊源.通過擴充套件了MessageSource介面,這些資訊源的擴充套件功能可以支援國際化的實現,為開發多語言版本提供支援
2. 訪問資源.這一特性體現在對ResourceLoader和Resource的支援上,這樣我們可以方便的從不同地方得到Bean定義資源,尤其是從不同的I/O途徑。
3. 支援應用事件,繼承了介面ApplicationEventPublisher,從而在上下文中引入了事件機制。這些事件和Bean的生命週期的結合為Bean的管理提供了便利。

ApplicationContext的設計原理

       我們以常用的FileSytemXmlApplicationContext的實現為例來說明ApplicationContext容器的設計原理.
在FileSystemXmlApplicationContext的設計中可以看到,可以看到ApplicationContext的主要功能已經在它的基類AbstractXmlApplicationContext中實現了。在FileSystemXmlApplicationContext中,只需要實現和它自身設計相關的兩個功能:
1. 如果直接使用FileSystemXmlApplicationContext,對於例項化這個ApplicationContext的支援,同時啟動IoC容器的refresh()過程。

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {

        super(parent);
        setConfigLocations(configLocations);
        if (refresh) {
            refresh();
        }
    }

refresh()過程會涉及到IoC容器啟動的一系列複雜操作,對於不同的容器實現,這些操作都是類似的,所以在基類中將它們封裝好。所以我們在FileSystemXml的設計中看到的只是一個簡單的呼叫。
2. 另一個功能是FileSystemXmlApplicationContext設計具體相關的功能,這部分與怎樣從檔案系統中載入XML的Bean定義資源有關。
通過這個過程,可以為在檔案系統中讀取以XML形式存在的BeanDefinition做準備,因為不同的ApplicationContext對應著不同的讀取BeanDefinition的方式,在FileSystemXmlApplicationContext中實現的程式碼如下:

@Override
    protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
        return new FileSystemResource(path);
    }

呼叫這個方法,就可以得到FileSystemResource的資源定位

IoC容器初始化的過程

看完再做總結~~每篇文章短點為好,太長的讓人看了失去耐性,能用短的篇幅把一個知識點說明白也是可以的~**歡迎大家一起討論學習喲**