天天看點

深入Spring IOC源碼之Resource

在java中,将不同來源的資源抽象成url,通過注冊不同的handler(urlstreamhandler)來處理不同來源的資源的讀取邏輯,一般handler的類型使用不同字首(協定,protocol)來識别,如“file:”、“http:”、“jar:”等,然而url沒有預設定義相對classpath或servletcontext等資源的handler,雖然可以注冊自己的urlstreamhandler來解析特定的url字首(協定),比如“classpath:”,然而這需要了解url的實作機制,而且url也沒有提供一些基本的方法,如檢查目前資源是否存在、檢查目前資源是否可讀等方法。因而spring對其内部使用到的資源實作了自己的抽象結構:resource接口來封裝底層資源:

public interface inputstreamsource {

    inputstream getinputstream() throws ioexception;

}

public interface resource extends inputstreamsource {

    boolean exists();

    boolean isreadable();

    boolean isopen();

    url geturl() throws ioexception;

    uri geturi() throws ioexception;

    file getfile() throws ioexception;

    long lastmodified() throws ioexception;

    resource createrelative(string relativepath) throws ioexception;

    string getfilename();

    string getdescription();

inputstreamsource封裝任何能傳回inputstream的類,比如file、classpath下的資源、byte array等。它隻有一個方法定義:getinputstream(),該方法傳回一個新的inputstream對象。

resource接口抽象了所有spring内部使用到的底層資源:file、url、classpath等。首先,它定義了三個判斷目前資源狀态的方法:存在性(exists)、可讀性(isreadable)、是否處于打開狀态(isopen)。在c語言中,當我們拿到一個檔案句柄時,我們要調用open方法打開檔案才可以真正讀取該檔案,但是在java中并沒有顯示的定義open方法,一般當我們建立一個inputstream、reader時,該資源(檔案)就已經處于打開狀态了,因而這裡的isopen方法并不是判斷目前資源是否已經處于打開的可操作狀态,這裡是表示resource接口所抽象的底層資源是否可以多次調用getinputstream()方法,如果該方法傳回true,則不可以多次調用getinputstream()方法。在spring 2.5.6的實作中,隻有inputstreamresource類的isopen()方法傳回true,其餘都傳回false。

另外,resource接口還提供了不同資源到url、uri、file類型的轉換,以及擷取lastmodified屬性、檔案名(不帶路徑資訊的檔案名,getfilename())的方法。為了便于操作,resource還提供了基于目前資源建立一個相對資源的方法:createrelative();在錯誤進行中需要詳細的列印出錯的資源檔案,因而resource還提供了getdescription()方法用于在錯誤進行中的列印資訊。

在spring 2.5.6中,所有實作resource的接口類繼承關系圖如下:

深入Spring IOC源碼之Resource

即對不同來源的資源檔案都有相應的resource實作:檔案(filesystemresource)、classpath資源(classpathresource)、url資源(urlresource)、inputstream資源(inputstreamresource)、byte數組(bytearrayresource)等。

abstractresource是對resource的基本實作,所有resource實作類都繼承了該類,所有繼承該類的resource一般隻需要實作以下方法即可:

public file getfile() throws ioexception

public url geturl() throws ioexception

public string getdescription()

public inputstream getinputstream() throws ioexception

該類預設實作中,将tostring、equals、hashcode都代理給description屬性;isreadable總是傳回true,而isopen總是傳回false;exists方法實作中,先調用getfile傳回的file對象的exists方法,如果失敗,檢視是否可以獲得inputstream,如果可以,傳回true,否則,傳回false;geturl和getfile、createrelative方法抛出filenotfoundexception,而geturi則代理給geturl方法。

bytearrayresource是一個簡單的resource實作,它是對二進制數組的封裝,每次調用getinputstream時都會以這個二進制數組作為源建立一個bytearrayinputstream。它的exists方法總是傳回true,而且重寫了equals、hashcode的方法,以判斷二進制數組的内容;它的description屬性可以是使用者自定義,也可以使用預設值:resource loaded from byte array

public final byte[] getbytearray() {

    return this.bytearray;

public boolean exists() {

    return true;

public inputstream getinputstream() throws ioexception {

    return new bytearrayinputstream(this.bytearray);

filesystemresource是對file的封裝,在建構filesystemresource時可以傳入file對象或路徑字元串(這裡的路徑可以是相對路徑,相對路徑是相對于system.getproperty(“user.dir”)的值所在的路徑,也可以是絕對路徑,也可以是“file:”開頭的路徑值),在内部會建立相應的file對象,并且計算其path值,這裡的path是計算完“.”和“..”影響的值(規格化)。

在getinputstream方法中,使用該file對象建立fileinputstream;而path值作為description屬性、equals、hashcode等方法的實作;所有其他方法(exists、isreadable、geturl等)都代理給file對象;createrelative方法中使用path計算相對路徑,其算法是:找到最後一個路徑分隔符(/),将相對路徑添加到該分隔符之後,傳入的相對路徑可以是以路徑分割符(/)開頭,也可以不以分隔符(/)開頭,他們的效果是一樣的,對相對路徑存在的“.”和“..”會在建立filesystemresource類時處理。最後,當使用将一個目錄的file對象建構filesystemresource時,調用createrelative方法,其相對路徑的父目錄和目前filesystemresource的父目錄相同,比如使用”/home/levin/dir1”目錄建立filesystemresource對象,該resource對象調用createrelative,并傳入”file”,那麼出現的結果為”/home/levin/file”,如果要得到”/home/levin/dir1/file”,那麼建構filesystemresource時,應該傳入”/home/levin/dir1/”字元串。

public boolean isreadable() {

    return (this.file.canread() && !this.file.isdirectory());

    return new fileinputstream(this.file);

public resource createrelative(string relativepath) {

    string pathtouse = stringutils.applyrelativepath(this.path, relativepath);

    return new filesystemresource(pathtouse);

public string getdescription() {

    return "file [" + this.file.getabsolutepath() + "]";

urlresource是對url和uri的封裝。在建構urlresource時可以傳入url、uri和path字元串(帶協定字元串,如”file:”)。在urlresource内部還會建立一個cleanedurl,它是規格化(計算“.”和“..”後的值),該url将會用于equals、hashcode方法的實作。

在getinputstream方法實作中,它使用url.openconnection()方法擷取urlconnection,後調用該urlconnection的getinputstream方法。對getfile()方法,隻支援檔案系統的資源,即url字元串的協定部分為”file:”。urlresource還支援從jar、zip、vfszip、wsjar等内部檔案,以jar為例,這些檔案的字元串表達為:jar:file:/<jarpath>/jarfile.jar!/<filepath>/filename,如jar:file:/e:/program%20files/eclipse-juno/plugins/org.junit_4.10.0.v4_10_0_v20120426-0900/junit.jar!/org/junit/test.class,然而這些内部檔案本身并沒有lastmodified的屬性,因而對這些内部檔案,urlresource将jar、zip等檔案的lastmodified視為這些内部檔案的lastmodified屬性。對createrelative方法,直接使用url提供的構造函數,忽略傳入的relativepath中的路徑分隔符“/”。

    urlconnection con = this.url.openconnection();

    con.setusecaches(false);

    return con.getinputstream();

protected file getfileforlastmodifiedcheck() throws ioexception {

    if (resourceutils.isjarurl(this.url)) {

        url actualurl = resourceutils.extractjarfileurl(this.url);

        return resourceutils.getfile(actualurl);

    }

    else {

        return getfile();

public resource createrelative(string relativepath) throws malformedurlexception {

    if (relativepath.startswith("/")) {

        relativepath = relativepath.substring(1);

    return new urlresource(new url(this.url, relativepath));

對classpath下資源的封裝,或者說是對classloader.getresource()方法或class.getresource()方法的封裝。它支援在目前classpath中讀取資源檔案。可以傳入相對classpath的檔案全路徑名和classloader建構classpathresource,或忽略classloader采用預設classloader(即thread context classloader),此時在getinputstream()方法實作時時會使用classloader.getresourceasstream()方法,由于使用classloader擷取資源時預設相對于classpath的根目錄,因而構造函數會忽略開頭的“/”字元。classpathresource還可以使用檔案路徑和class作為參數建構,此時若檔案路徑以“/”開頭,表示該檔案為相對于classpath的絕對路徑,否則為相對class執行個體的相對路徑,在getinputstream()方法實作時使用class.getresourceasstream()方法。

getfile()方法隻支援存在于檔案系統中的資源;對lastmodified的屬性,若是jar、zip等檔案中的資源,則采用jar、zip檔案本身的lastmodified屬性;equals會同時判斷path、classloader、clazz字段,而hashcode則隻使用path。

    inputstream is = null;

    if (this.clazz != null) {

        is = this.clazz.getresourceasstream(this.path);

        is = this.classloader.getresourceasstream(this.path);

    if (is == null) {

        throw new filenotfoundexception(

                getdescription() + " cannot be opened because it does not exist");

    return is;

public url geturl() throws ioexception {

    url url = null;

        url = this.clazz.getresource(this.path);

        url = this.classloader.getresource(this.path);

    if (url == null) {

                getdescription() + " cannot be resolved to url because it does not exist");

    return url;

public file getfile() throws ioexception {

    return resourceutils.getfile(geturl(), getdescription());

    url url = geturl();

    if (resourceutils.isjarurl(url)) {

        url actualurl = resourceutils.extractjarfileurl(url);

        return resourceutils.getfile(url, getdescription());

    return new classpathresource(pathtouse, this.classloader, this.clazz);

inputstreamresource是對inputstream的封裝,它接收inputstream作為構造函數參數,它的isopen總是傳回true,并且隻能被讀取一次(即getinputstream方法隻能被調用一次),exists、isreadable方法也總是傳回true。由于它不能被多次讀取,隻有當不用多次讀取的時候才使用該類,并且隻有當沒有其他可用resource類時才使用該類。在spring内部貌似沒有使用它。它隻實作了getinputstream方法:

public inputstream getinputstream() throws ioexception, illegalstateexception {

    if (this.read) {

        throw new illegalstateexception("inputstream has already been read - " +

                "do not use inputstreamresource if a stream needs to be read multiple times");

    this.read = true;

    return this.inputstream;

descriptiveresource是對非實體資源的description的封裝。它實作了getdescription()方法。resource中description屬性主要用于錯誤處理時能更加準确的列印出錯位置的資訊,descriptiveresource提供對那些需要提供resource接口中的description屬性作為錯誤列印資訊的方法自定義的描述資訊。比如在beandefinitionreader中,在僅僅使用inputsource作為源加載beandefinition時,就可以使用descriptiveresource定義自己的description,進而在出錯資訊中可以友善的知道問題源在哪裡。

在spring中resource可以用于非實體資源的抽,beandefinitionresource是對beandefinition的封裝。beandefinitionresource類似descriptiveresource,它也隻實作了getdescription()方法,用于在解析某個beandefinition出錯時顯示錯誤源資訊:

    return "beandefinition defined in " + this.beandefinition.getresourcedescription();

在spring中還定義了contextresource接口,繼承自resource接口,隻包含一個方法:

public interface contextresource extends resource {

    string getpathwithincontext();

getpathwithcontext()方法相對于context的路徑,如servletcontext、portletcontext、classpath、filesystem等,在spring core中它有兩個實作類filesystemcontextresource、classpathcontextresource,他們分别是filesystemresourceloader和defaultresourceloader中的内部類,他們對getpathwithcontext()方法的實作隻是簡單的傳回path值。

另外,在spring web子產品中,有一個servletcontextresource實作類,它使用servletcontext和path作為參數構造,getinputstream、geturl、geturi、getfile等方法中将實作代理給servletcontext,其中getpathwithcontext方法依然傳回path字元串:

    try {

        url url = this.servletcontext.getresource(this.path);

        return (url != null);

    catch (malformedurlexception ex) {

        return false;

    inputstream is = this.servletcontext.getresourceasstream(this.path);

        throw new filenotfoundexception("could not open " + getdescription());

    url url = this.servletcontext.getresource(this.path);

    string realpath = webutils.getrealpath(this.servletcontext, this.path);

    return new file(realpath);

    return new servletcontextresource(this.servletcontext, pathtouse);