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,將拋出IOExceptiongetURI()
: 返回一個資源的URI句柄getFile()
: 返回某個文件,如果資源不能夠被解析稱為絕對路徑,將會拋出FileNotFoundExceptionlastModified()
: 資源最后一次修改的時間戳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。下述兩個例子分別表示使用http
和file
前綴。
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 實例。
類似於BeanNameAware
,BeanFactoryAware
接口,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");