Spring Resource框架體系介紹


Resource介紹

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

JavaDoc解釋

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

Resource接口方法

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

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所沒有的方法

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

public interface InputStreamSource {

	InputStream getInputStream() throws IOException;

}

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

  • 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。

代碼表示:

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");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM