1. 程式人生 > >Spring原始碼學習--容器的基礎XMLBeanFactory(配置檔案封裝)

Spring原始碼學習--容器的基礎XMLBeanFactory(配置檔案封裝)

文章引用:

Spring中配置檔案的載入通過ClassPathResource進行封裝的,例如:

Resource resource = new ClassPathResource("applicationContext.xml");

一、ClassPathResource的UML圖如下所示

這裡寫圖片描述

先來看看在Spring原始碼深度解析這本書中描述的為什麼Spring要有ClassPathResource這個東西吧

在java中,將不同來源的資源抽象成URL,通過註冊不同的handler(RULStreamHandler)來處理不同來源的讀取邏輯,一般handler的型別使用不同字首(協議,Protocol)來識別,如”file:”、”http:”、”jar:”等,然後URL沒有預設定義相對ClassPath或者ServletContext等資源的handler,雖然可以註冊自己的URLStreamHandler來解析特定的URL字首(協議),比如”classpath:”,然後這需要了解URL的實現機制,而且URL也沒有提供一些基本的方法,比如檢查當前資源是否存在、檢查當前資源是否可讀等方法。因而Spring對其內部使用到的資源實現了自己的抽象結構:Resource介面來封裝底層資源。

二、InputStreamSource介面

這是針對 InputStream 提供的 Resource 實現。建議,在確實沒有找到其他合適的 Resource 實現時,才使用 InputSteamResource。如果可以,儘量選擇 ByteArrayResource 或其他基於檔案的 Resource 實現來代替。

與其他 Resource 實現已比較,InputStreamRsource 倒像一個已開啟資源的描述符,因此,呼叫 isOpen() 方法會返回 true。除了在需要獲取資源的描述符或需要從輸入流多次讀取時,都不要使用 InputStreamResource 來讀取資源。

package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * InputStreamSource 介面定義了一種能力:InputStreamSource封裝任何能夠返回InputStream的類,比如
 * File\ClassPath下的資源和Byte和Array等。
 * 
 * 定位並且開啟當前資源,返回當前資源的 InputStream。預計每一次呼叫都會返回一個新的 InputStream,
 * 因此關閉當前輸出流就成為了呼叫者的責任。
 */
/** * Simple interface for objects that are sources for an {@link InputStream}. * * <p>This is the base interface for Spring's more extensive {@link Resource} interface. * * <p>For single-use streams, {@link InputStreamResource} can be used for any * given {@code InputStream}. Spring's {@link ByteArrayResource} or any * file-based {@code Resource} implementation can be used as a concrete * instance, allowing one to read the underlying content stream multiple times. * This makes this interface useful as an abstract content source for mail * attachments, for example. * * @author Juergen Hoeller * @since 20.01.2004 * @see java.io.InputStream * @see Resource * @see InputStreamResource * @see ByteArrayResource */ public interface InputStreamSource { /** * Return an {@link InputStream}. * <p>It is expected that each call creates a <i>fresh</i> stream. * <p>This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is <i>required</i> * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws IOException if the stream could not be opened * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource) */ InputStream getInputStream() throws IOException; }

三、Resource 介面

相對標準 url 訪問機制,spring 的 Resource 介面對抽象底層資源的訪問提供了一套更好的機制。

/**
 * Resource介面抽象了所有Spring內部使用到的底層資源:File、URL、Classpath等。首先,它定義了3個判斷當前資源狀態的方法:
 * exists()、isReadable()、isOpen()。Resource介面還提供了不同資源到URL、URI、File型別的轉換;
 * 以及獲取lastModified屬性、檔名(不帶路徑資訊的檔名、getFilename()方法)。為了便於操作,Resource還提供了
 * 基於當前資源建立一個相對資源的方法:createRelative()。在錯誤處理中需要詳細地打印出錯的資原始檔,因而Resource
 * 還提供了getDescription()方法用於在錯誤處理中的列印資訊。
 */
public interface Resource extends InputStreamSource {

    /**
     * 判斷當前資源是否存在
     */
    boolean exists();

    /**
     * 判斷當前資源是否可讀
     */
    boolean isReadable();

    /**
     * 判斷當前資源是否開啟狀態
     */
    boolean isOpen();

    /**
     * 獲取檔名稱(不帶路徑資訊的檔名)
     */
    String getFilename();

    /**
     * 返回當前資源的描述,當處理資源出錯時,資源的描述會用於錯誤資訊的輸出。
     * 一般來說,資源的描述是一個完全限定的檔名稱,或者是當前資源的真實 url。
     */
    String getDescription();

    long lastModified() throws IOException;

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    long contentLength() throws IOException;
}

四、內建的 Resource 實現

spring 直接提供了多種開箱即用的 Resource 實現。

1丶UrlResource

UrlResource 封裝了一個 java.net.URL 物件,用來訪問 URL 可以正常訪問的任意物件,比如檔案、an HTTP target, an FTP target, 等等。所有的 URL 都可以用一個標準化的字串來表示。如通過正確的標準化字首,可以用來表示當前 URL 的型別,當中就包括用於訪問檔案系統路徑的 file:,通過 http 協議訪問資源的 http:,通過 ftp 協議訪問資源的 ftp:,還有很多……

可以顯式化地使用 UrlResource 建構函式來建立一個 UrlResource,不過通常我們可以在呼叫一個 api 方法是,使用一個代表路徑的 String 引數來隱式建立一個 UrlResource。對於後一種情況,會由一個 javabean PropertyEditor 來決定建立哪一種 Resource。如果路徑裡包含某一個通用的字首(如 classpath:),PropertyEditor 會根據這個通用的字首來建立恰當的 Resource;反之,如果 PropertyEditor 無法識別這個字首,會把這個路徑作為一個標準的 URL 來建立一個 UrlResource。

2丶ClassPathResource

可以使用 ClassPathResource 來獲取類路徑上的資源。ClassPathResource 可以使用執行緒上下文的載入器、呼叫者提供的載入器或指定的類中的任意一個來載入資源。

ClassPathResource 可以從類路徑上載入資源,其可以使用執行緒上下文載入器、指定載入器或指定的 class 型別中的任意一個來載入資源。

當類路徑上資源存於檔案系統中,ClassPathResource 支援以 java.io.File 的形式訪問,可當類路徑上的資源存於尚未解壓(沒有 被Servlet 引擎或其他可解壓的環境解壓)的 jar 包中,ClassPathResource 就不再支援以 java.io.File 的形式訪問。鑑於上面所說這個問題,spring 中各式 Resource 實現都支援以 jave.net.URL 的形式訪問。

可以顯式使用 ClassPathResource 建構函式來建立一個 ClassPathResource ,不過通常我們可以在呼叫一個 api 方法時,使用一個代表路徑的 String 引數來隱式建立一個 ClassPathResource。對於後一種情況,會由一個 javabean PropertyEditor 來識別路徑中 classpath: 字首,從而建立一個 ClassPathResource。

3丶FileSystemResource

這是針對 java.io.File 提供的 Resource 實現。顯然,我們可以使用 FileSystemResource 的 getFile() 函式獲取 File 物件,使用 getURL() 獲取 URL 物件。

4丶ServletContextResource

這是為了獲取 web 根路徑的 ServletContext 資源而提供的 Resource 實現。

ServletContextResource 完全支援以流和 URL 的方式訪問,可只有當 web 專案是已解壓的(不是以 war 等壓縮包形式存在)且該 ServletContext 資源存於檔案系統裡,ServletContextResource 才支援以 java.io.File 的方式訪問。至於說到,我們的 web 專案是否已解壓和相關的 ServletContext 資源是否會存於檔案系統裡,這個取決於我們所使用的 Servlet 容器。若 Servlet 容器沒有解壓 web 專案,我們可以直接以 JAR 的形式的訪問,或者其他可以想到的方式(如訪問資料庫)等。

5丶InputStreamResource

這是針對 InputStream 提供的 Resource 實現。建議,在確實沒有找到其他合適的 Resource 實現時,才使用 InputSteamResource。如果可以,儘量選擇 ByteArrayResource 或其他基於檔案的 Resource 實現來代替。

與其他 Resource 實現已比較,InputStreamRsource 倒像一個已開啟資源的描述符,因此,呼叫 isOpen() 方法會返回 true。除了在需要獲取資源的描述符或需要從輸入流多次讀取時,都不要使用 InputStreamResource 來讀取資源。

6丶ByteArrayResource

這是針對位元組陣列提供的 Resource 實現。可以通過一個位元組陣列來建立 ByteArrayResource。

當需要從位元組陣列載入內容時,ByteArrayResource 是一個不錯的選擇,使用 ByteArrayResource 可以不用求助於 InputStreamResource。

五、ClassPathResource

1、獲取ClassLoader的幾種方式:

this.getClass.getClassLoader();  // 使用當前類的ClassLoader   

Thread.currentThread().getContextClassLoader();  // 使用當前執行緒的ClassLoader   

ClassLoader.getSystemClassLoader();  // 使用系統ClassLoader,即系統的入口點所使用的ClassLoader。  

2、利用ClassLoader獲取配置檔案的InputeStream

InputStream is = null;
try {
    is = this.getClass().getClassLoader().getResourceAsStream("com/alexia/config/sys.properties");

    is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/alexia/config/sys.properties");

    is = ClassLoader.getSystemResourceAsStream("com/alexia/config/sys.properties");
} catch (Exception e) {

} finally {
    IOUtils.closeQuietly(is);
}

3、其中ClassPathResource中InputStreamSource介面中getInputStream()方法實現如下

    /**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }

4、上述this.clazz.getResourceAsStream(this.path)方法描述如下:

這裡寫圖片描述

五、業務開發關聯

學習原始碼的一個重要目的就是為了使用原始碼

在日常的開發工作中,資原始檔的載入也是經常用到的,可以直接使用Spring提供的類,比如在希望載入檔案時可以使用一下程式碼:

Resource resource = new ClassPathResource("applicationContext.xml");
InputStream inputStream = resource.getInputStream();

利用上述程式碼我們得到inputStream之後,我們就可以按照以前的開發方式進行實現,並且我們已經可以利用Resource以及子類為我們提供好的諸多特性。

有了Resource介面邊可以對所有資原始檔進行統一處理。至於實現,其實是非常簡單的,以getInputStream為例,ClassPathResource中的實現方式便是通過class或者classloader提供的底層方法進行呼叫,而對於FileSystemResource的實現其實更簡單,直接使用FileInputStream對檔案進行例項化。

FileSystemResource中getInputStream方法實現如下:

    /**
     * This implementation opens a FileInputStream for the underlying file.
     * @see java.io.FileInputStream
     */
    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

當通過Resource相關類完成了對配置檔案進行封裝後配置檔案的讀取工作就完全交給XmlBeanDefinitionReader來處理了。

最後出給幾種獲取ClassLoader方法,並利用ClassLoader方法獲取配置檔案資訊方法,程式碼GitHub地址: