Spring Resource框架體系介紹

Resource介紹

在使用spring做爲容器進行項目開發中會有不少的配置文件,這些配置文件都是經過Spring的Resource接口來實現加載,可是,Resource對於全部低級資源的訪問都不夠充分。例如,沒有標準化的URL實現可用於訪問須要從類路徑或相對於ServletContext獲取的資源。(更多關於ServletContext的理解,請訪問https://www.cnblogs.com/cxuanBlog/p/10927813.html)雖然能夠爲專用的URL前綴註冊新的處理程序(相似於http :)這樣的前綴的現有處理程序,但這一般很是複雜,而且URL接口仍然缺乏一些理想的功能,例如檢查存在的方法被指向的資源。html

JavaDoc解釋

從實際類型的底層資源(例如文件或類路徑資源)中抽象出來的資源描述符的接口。java

Resource接口方法

Spring的Resource接口旨在成爲一個更有能力的接口,用於抽象對低級資源的訪問。如下清單顯示了Resource接口定義git

public interface Resource extends InputStreamSource {
 
  boolean exists();
  
  default boolean isReadable() {
        return true;
    }
  
  default boolean isOpen() {
        return false;
    }
  
  default boolean isFile() {
        return false;
    }
  
  URL getURL() throws IOException;
  
  URI getURI() throws IOException;
  
  File getFile() throws IOException;
  
  default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(getInputStream());
    }
  
  long contentLength() throws IOException;
  
  long lastModified() throws IOException;
  
  Resource createRelative(String relativePath) throws IOException;
  
    String getFilename();
  
  String getDescription();
}

Resource接口繼承了InputStreamSource接口,提供了不少InputStreamSource所沒有的方法github

下面來看一下InputStreamSource接口,只有一個方法web

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

其中一些大部分重要的接口是:spring

  • getInputStream(): 找到並打開資源,返回一個InputStream以從資源中讀取。預計每次調用都會返回一個新的InputStream(),調用者有責任關閉每一個流
  • exists(): 返回一個布爾值,代表某個資源是否以物理形式存在
  • isOpen: 返回一個布爾值,指示此資源是否具備開放流的句柄。若是爲true,InputStream就不可以屢次讀取,只可以讀取一次而且及時關閉以免內存泄漏。對於全部常規資源實現,返回false,可是InputStreamResource除外。
  • getDescription(): 返回資源的描述,用來輸出錯誤的日誌。這一般是徹底限定的文件名或資源的實際URL。

其餘方法:數據庫

  • isReadable(): 代表資源的目錄讀取是否經過getInputStream()進行讀取。
  • isFile(): 代表這個資源是否表明了一個文件系統的文件。
  • getURL(): 返回一個URL句柄,若是資源不可以被解析爲URL,將拋出IOException
  • getURI(): 返回一個資源的URI句柄
  • getFile(): 返回某個文件,若是資源不可以被解析稱爲絕對路徑,將會拋出FileNotFoundException
  • lastModified(): 資源最後一次修改的時間戳
  • createRelative(): 建立此資源的相關資源
  • getFilename(): 資源的文件名是什麼 例如:最後一部分的文件名 myfile.txt

Resource的實現類

Resource 接口是 Spring 資源訪問策略的抽象,它自己並不提供任何資源訪問實現,具體的資源訪問由該接口的實現類完成——每一個實現類表明一種資源訪問策略。數組

基礎類介紹

Resource通常包括這些實現類:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource網絡

使用UrlResource訪問網絡資源

訪問網絡資源的實現類。Resource的一個實現類用來定位URL中的資源。它支持URL的絕對路徑,用來做爲file: 端口的一個資源,建立一個maven項目,配置Spring依賴(再也不贅述)和dom4j 的依賴,並在根目錄下建立一個books.xml。app

代碼表示:

public class UrlResourceTest {

    public static void loadAndReadUrlResource(String path) throws Exception{
        // 建立一個 Resource 對象,指定從文件系統裏讀取資源,相對路徑
        UrlResource resource = new UrlResource(path);
        // 絕對路徑
//        UrlResource resource = new UrlResource("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        // 獲取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 獲取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        SAXReader reader = new SAXReader();
        System.out.println(resource.getFile());
        Document document = reader.read("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        Element parent = document.getRootElement();
        List<Element> elements = parent.elements();
        for(Element element : elements){
            // 獲取name,description,price
            System.out.println(element.getName() + " = " +element.getText());
        }
    }


    public static void main(String[] args) throws Exception {
        loadAndReadUrlResource("file:books.xml");
    }

}

上面程序使用UrlResource來訪問網絡資源,也能夠經過file 前綴訪問本地資源,上述代碼就是這樣作的。若是要訪問網絡資源,能夠有兩種形式

  • http:-該前綴用於訪問基於 HTTP 協議的網絡資源。
  • ftp:-該前綴用於訪問基於 FTP 協議的網絡資源。

使用ClassPathResource 訪問類加載路徑下的資源

ClassPathResource 用來訪問類加載路徑下的資源,相對於其餘的 Resource 實現類,其主要優點是方便訪問類加載路徑裏的資源,尤爲對於 Web 應用,ClassPathResource 可自動搜索位於 WEB-INF/classes 下的資源文件,無須使用絕對路徑訪問。

public class ClassPathResourceTest {

    public static void loadAndReadUrlResource(String path) throws Exception{
        // 建立一個 Resource 對象,指定從文件系統裏讀取資源,相對路徑
        ClassPathResource resource = new ClassPathResource(path);
        // 絕對路徑
//        UrlResource resource = new UrlResource("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        // 獲取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 獲取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        SAXReader reader = new SAXReader();
        System.out.println(resource.getPath());
        Document document = reader.read("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        Element parent = document.getRootElement();
        List<Element> elements = parent.elements();
        for(Element element : elements){
            // 獲取name,description,price
            System.out.println(element.getName() + " = " +element.getText());
        }
    }


    public static void main(String[] args) throws Exception {
        loadAndReadUrlResource("books.xml");
    }
}

除了以上新建方式的不一樣,其餘代碼和上述代碼一致,這就是 Spring 資源訪問的優點:Spring 的資源訪問消除了底層資源訪問的差別,容許程序以一致的方式來訪問不一樣的底層資源。

使用FileSystemResource 訪問文件資源系統

Spring 提供的 FileSystemResource 類用於訪問文件系統資源,使用 FileSystemResource 來訪問文件系統資源並無太大的優點,由於 Java 提供的 File 類也可用於訪問文件系統資源。

固然使用 FileSystemResource 也可消除底層資源訪問的差別,程序經過統一的 Resource API 來進行資源訪問。下面程序是使用 FileSystemResource 來訪問文件系統資源的示例程序。

public class FileSystemResourceTest {

    public static void loadAndReadUrlResource(String path) throws Exception{
        // 建立一個 Resource 對象,指定從文件系統裏讀取資源,相對路徑
        FileSystemResource resource = new FileSystemResource(path);
        // 絕對路徑
//        UrlResource resource = new UrlResource("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        // 獲取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 獲取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        SAXReader reader = new SAXReader();
        System.out.println(resource.getFile());
        Document document = reader.read("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        Element parent = document.getRootElement();
        List<Element> elements = parent.elements();
        for(Element element : elements){
            // 獲取name,description,price
            System.out.println(element.getName() + " = " +element.getText());
        }
    }


    public static void main(String[] args) throws Exception {
        loadAndReadUrlResource("books.xml");
    }
}

FileSystemResource 實例可以使用 FileSystemResource 構造器顯式地建立。但更多的時候它都是隱式建立的,執行 Spring 的某個方法時,該方法接受一個表明資源路徑的字符串參數,當 Spring 識別該字符串參數中包含 file: 前綴後,系統將會自動建立 FileSystemResource 對象。

ServletContextResource

這是ServletContext資源的Resource實現,它解釋相關Web應用程序根目錄中的相對路徑。

它始終支持流(stream)訪問和URL訪問,但只有在擴展Web應用程序存檔且資源實際位於文件系統上時才容許java.io.File訪問。不管它是在文件系統上擴展仍是直接從JAR或其餘地方(如數據庫)訪問,實際上都依賴於Servlet容器。

InputStreamResource

InputStreamResource 是給定的輸入流(InputStream)的Resource實現。它的使用場景在沒有特定的資源實現的時候使用(感受和@Component 的適用場景很類似)。

與其餘Resource實現相比,這是已打開資源的描述符。 所以,它的isOpen()方法返回true。若是須要將資源描述符保留在某處或者須要屢次讀取流,請不要使用它。

ByteArrayResource

字節數組的Resource實現類。經過給定的數組建立了一個ByteArrayInputStream

它對於從任何給定的字節數組加載內容很是有用,而無需求助於單次使用的InputStreamResource。

Resource類圖與策略模式

上述Resource實現類與Resource頂級接口之間的關係能夠用下面的UML關係模型來表示

策略模式

上述流程圖是否是對同一行爲的不一樣實現方式,這種實現方式像極了策略模式?具體關於策略模式的文章,請參考

https://www.runoob.com/design-pattern/strategy-pattern.html

ResourceLoader 接口

ResourceLoader接口旨在由能夠返回(即加載)Resource實例的對象實現,該接口實現類的實例將得到一個 ResourceLoader 的引用。下面是ResourceLoader的定義

public interface ResourceLoader {
        
    //該接口僅包含這個方法,該方法用於返回一個 Resource 實例。ApplicationContext 的實現類都實現       ResourceLoader 接口,所以 ApplicationContext 可用於直接獲取 Resource 實例
    Resource getResource(String location);

}

全部的應用程序上下文都實現了ResourceLoader接口。所以,全部的應用程序上下文均可能會獲取Resource實例。

在特定應用程序上下文上調用getResource()而且指定的位置路徑沒有特定前綴時,將返回適合該特定應用程序上下文的Resource類型。 例如,假設針對ClassPathXmlApplicationContext實例執行了如下代碼:

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

你暫時不知道具體的上下文資源類型是什麼,假設指定的是ClassPathXmlApplicationContext,上述代碼就會返回ClassPathResource,若是執行上面相同的方法的是FileSystemXmlApplicationContext,上述代碼就會返回的是FileSystemResource,對於web系統來講,若是上下文容器時候WebApplicationContext,那麼返回的將是ServletContextResource,它一樣會爲每一個上下文返回適當的對象。所以,您能夠以適合特定應用程序上下文的方式加載資源。

另外一方面,你可能強制使用ClassPathResource,忽略應用程序的上下文類型,經過添加特定的前綴classpath:,如下示例說明了這一點。

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");

一樣的,你可以強制使用UrlResource經過使用特定的前綴:java.net.URL。下述兩個例子分別表示使用httpfile前綴。

Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");

Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

下列表格對資源類型和前綴進行更好的彙總:

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml 從類路徑加載
file: file:///data/config.xml 從文件系統加載做爲URL,查閱FileSystemResource
http: https://myserver/logo.png 加載做爲URL
(none) /data/config.xml 依賴於ApplicationContext

ResourceLoaderAware 接口

這個ResourceLoaderAware接口是一個特殊的回調接口,用於標識但願隨ResourceLoader引用提供的組件,下面是ResourceLoaderAware 接口的定義

public interface ResourceLoaderAware extends Aware {

    void setResourceLoader(ResourceLoader resourceLoader);
}

ResourceLoaderAware 接口用於指定該接口的實現類必須持有一個 ResourceLoader 實例。

相似於BeanNameAwareBeanFactoryAware接口,ResourceLoaderAware接口也提供了一個setResourceLoader()方法,該方法由Spring容器負責,Spring 容器會將一個 ResourceLoader 對象做爲該方法的參數傳入。

固然了,一個 bean 若想加載指定路徑下的資源,除了剛纔提到的實現 ResourcesLoaderAware 接口以外(將 ApplicationContext 做爲一個 ResourceLoader 對象注入),bean 也能夠實現 ApplicationContextAware 接口,這樣能夠直接使用應用上下文來加載資源。但總的來講,在需求知足都知足的狀況下,最好是使用的專用 ResourceLoader 接口,由於這樣代碼只會與接口耦合,而不會與整個 spring ApplicationContext 耦合。與 ResourceLoader 接口耦合,拋開 spring 來看,就是提供了一個加載資源的工具類接口。因爲ApplicationContext也是一個ResourceLoader,所以bean還能夠實現ApplicationContextAware接口並直接使用提供的應用程序上下文來加載資源。可是,一般狀況下,若是有須要的話最好仍是使用特定的ResourceLoader接口。

在應用程序的組件中,除了實現 ResourceLoaderAware 接口,也可採起另一種替代方案——依賴於 ResourceLoader 的自動裝配。傳統的構造函數注入和byType自動裝配模式(如自動裝配協做者中所述)可以分別爲構造函數參數或setter方法參數提供ResourceLoader。若爲了得到更大的靈活性(包括屬性注入的能力和多參方法),能夠考慮使用基於註解的新注入方式。使用註解 @Autowiring 標記 ResourceLoader 變量,即可將其注入到成員屬性、構造參數或方法參數中。

使用Resource做爲屬性

前面介紹了 Spring 提供的資源訪問策略,但這些依賴訪問策略要麼須要使用 Resource 實現類,要麼須要使用 ApplicationContext 來獲取資源。實際上,當應用程序中的 Bean 實例須要訪問資源時,Spring 有更好的解決方法:直接利用依賴注入。

從這個意義上來看,Spring 框架不只充分利用了策略模式來簡化資源訪問,並且還將策略模式和 IoC 進行充分地結合,最大程度地簡化了 Spring 資源訪問。

概括起來,若是 Bean 實例須要訪問資源,有以下兩種解決方案:

  • 代碼中獲取 Resource 實例。
  • 使用依賴注入。

對於第一種方式的資源訪問,當程序獲取 Resource 實例時,總須要提供 Resource 所在的位置,無論經過 FileSystemResource 建立實例,仍是經過 ClassPathResource 建立實例,或者經過 ApplicationContext 的 getResource() 方法獲取實例,都須要提供資源位置。這意味着:資源所在的物理位置將被耦合到代碼中,若是資源位置發生改變,則必須改寫程序。所以,一般建議採用第二種方法,讓 Spring 爲 Bean 實例依賴注入資源。

如下示例說明了這一點(可使用set方法注入):

public class TestBean {

    private Resource resource;

    public Resource getResource() {
        return resource;
    }

    public void setResource(Resource resource) {
        this.resource = resource;
    }

    public void parse() throws Exception {
        // 獲取文件名
        System.out.println("resource.getFileName = " + resource.getFilename());
        // 獲取文件描述
        System.out.println("resource.getDescription = "+ resource.getDescription());
        SAXReader reader = new SAXReader();
        Document document = reader.read("file:///Users/mr.l/test/CXuan-Spring/CXuan-Spring-Resource/books.xml");
        Element parent = document.getRootElement();
        List<Element> elements = parent.elements();
        for(Element element : elements){
            // 獲取name,description,price
            System.out.println(element.getName() + " = " +element.getText());
        }
    }

    public static void main(String[] args) throws Exception {
        TestBean testBean = new TestBean();
        testBean.setResource(new ClassPathResource("beans.xml"));
        testBean.parse();
    }
}

上面配置文件配置了資源的位置,並使用了 classpath: 前綴,這指明讓 Spring 從類加載路徑里加載 book.xml 文件。與前面相似的是,此處的前綴也可採用 http:、ftp: 等,這些前綴將強制 Spring 採用怎樣的資源訪問策略(也就是指定具體使用哪一個 Resource 實現類);若是不採用任何前綴,則 Spring 將採用與該 ApplicationContext 相同的資源訪問策略來訪問資源。

<property name="template" value="classpath:some/resource/path/myTemplate.txt">
<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

應用程序上下文和資源路徑

本節介紹如何使用資源建立應用程序上下文,包括使用XML的快捷方式,如何使用通配符以及其餘詳細信息。

構造應用程序上下文

應用程序上下文構造函數(對於特定的應用程序上下文類型)一般將字符串或字符串數組做爲資源的位置路徑,例如構成上下文定義的XML文件。

當這樣的位置路徑沒有前綴時,從該路徑構建並用於加載bean定義的特定資源類型取決於而且適合於特定的應用程序上下文。 例如,請考慮如下示例,該示例建立ClassPathXmlApplicationContext:

ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

bean 定義從類路徑中加載,由於ClassPathResource被使用了,然而,考慮如下例子,建立了一個FileSystemXmlApplicationContext:

ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

如今bean的定義信息會從文件系統中加載,請注意,在位置路徑上使用特殊類路徑前綴或標準URL前綴會覆蓋爲加載定義而建立的默認資源類型。 請考慮如下示例:

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

建立 Spring 容器時,系統將從類加載路徑來搜索 appContext.xml;但使用 ApplicationContext 來訪問資源時,依然採用的是 FileSystemResource 實現類,這與 FileSystemXmlApplicationContext 的訪問策略是一致的。這代表:經過 classpath: 前綴指定資源訪問策略僅僅對當次訪問有效,程序後面進行資源訪問時,仍是會根據 AppliactionContext 的實現類來選擇對應的資源訪問策略。

應用程序上下文路徑中的通配符

上下文構造資源的路徑多是一些簡單路徑,可是對於每個映射來講,不可能只有簡單路徑,也會有特殊複雜的路徑出現,這就須要使用到路徑通配符(ant-style)。

ant-style示例

/WEB-INF/*-context.xml
com/mycompany/**/applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/**/applicationContext.xml

classpath* 和 classpath的區別:

classpath: 當使用 classpath :時前綴來指定 XML 配置文件時,系統將搜索類加載路徑,找出全部與文件名的文件,分別裝載文件中的配置定義,最後合併成一個 ApplicationContext。

public static void main(String[] args) throws Exception { 
  // 使用 classpath* 裝載多份配置文件輸出 ApplicationContext 實例。
  ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath*:bean.xml");
  System.out.println(ctx); 
}

若是不是採用 classpath*: 前綴,而是改成使用 classpath: 前綴,Spring 只加載第一份符合條件的 XML 文件,例如以下代碼

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:bean.xml");

當使用 classpath: 前綴時,系統經過類加載路徑搜索 bean.xml 文件,若是找到文件名匹配的文件,系統當即中止搜索,裝載該文件,即便有多份文件名匹配的文件,系統只裝載第一份文件。

路徑匹配

另外,還有一種能夠一次性裝載多份配置文件的方式:指定配置文件時指定使用通配符,例如以下代碼:

ApplicationContext ctx = new ClassPathXmlApplicationContext("bean*.xml");

除此以外,Spring 甚至容許將 classpath*: 前綴和通配符結合使用,以下語句也是合法的:

ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath*:bean*.xml");

file 前綴的用法

相對路徑的寫法:

ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");

絕對路徑的寫法:

ApplicationContext ctx = new FileSystemXmlApplicationContext("/bean.xml");

若是程序中須要訪問絕對路徑,則不要直接使用 FileSystemResource 或 FileSystemXmlApplicationContext 來指定絕對路徑。建議強制使用 file: 前綴來區分相對路徑和絕對路徑,例如以下兩行代碼

ApplicationContext ctx = new FileSystemXmlApplicationContext("file:bean.xml"); 
ApplicationContext ctx = new FileSystemXmlApplicationContext("file:/bean.xml");

文章參考:

Spring官方文檔: https://docs.spring.io/spring/docs/5.1.7.RELEASE/spring-framework-reference/core.html#resources

IBM使用手冊:https://www.ibm.com/developerworks/cn/java/j-lo-spring-resource/index.html

相關文章
相關標籤/搜索