在Java中,將不同來源的資源抽象成URL,通過注冊不同的handler(URLStreamHandler)來處理不同來源的資源的讀取邏輯,一般handler的類型使用不同前綴(協(xié)議,protocol)來識別,如“file:”、“http:”、“jar:”等,然而URL沒有默認定義相對classpath或ServletContext等資源的handler,雖然可以注冊自己的URLStreamHandler來解析特定的URL前綴(協(xié)議),比如“classpath:”,然而這需要了解URL的實現(xiàn)機制,而且URL也沒有提供一些基本的方法,如檢查當(dāng)前資源是否存在、檢查當(dāng)前資源是否可讀等方法。因而Spring對其內(nèi)部使用到的資源實現(xiàn)了自己的抽象結(jié)構(gòu):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內(nèi)部使用到的底層資源:File、URL、classpath等。首先,它定義了三個判斷當(dāng)前資源狀態(tài)的方法:存在性(exists)、可讀性(isReadable)、是否處于打開狀態(tài)(isOpen)。在C語言中,當(dāng)我們拿到一個文件句柄時,我們要調(diào)用open方法打開文件才可以真正讀取該文件,但是在Java中并沒有顯示的定義open方法,一般當(dāng)我們創(chuàng)建一個InputStream、Reader時,該資源(文件)就已經(jīng)處于打開狀態(tài)了,因而這里的isOpen方法并不是判斷當(dāng)前資源是否已經(jīng)處于打開的可操作狀態(tài),這里是表示Resource接口所抽象的底層資源是否可以多次調(diào)用getInputStream()方法,如果該方法返回true,則不可以多次調(diào)用getInputStream()方法。在Spring 2.5.6的實現(xiàn)中,只有InputStreamResource類的isOpen()方法返回true,其余都返回false。
另外,Resource接口還提供了不同資源到URL、URI、File類型的轉(zhuǎn)換,以及獲取lastModified屬性、文件名(不帶路徑信息的文件名,getFilename())的方法。為了便于操作,Resource還提供了基于當(dāng)前資源創(chuàng)建一個相對資源的方法:createRelative();在錯誤處理中需要詳細的打印出錯的資源文件,因而Resource還提供了getDescription()方法用于在錯誤處理中的打印信息。
在Spring 2.5.6中,所有實現(xiàn)Resource的接口類繼承關(guān)系圖如下:

即對不同來源的資源文件都有相應(yīng)的Resource實現(xiàn):文件(FileSystemResource)、classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte數(shù)組(ByteArrayResource)等。
AbstractResource類
AbstractResource是對Resource的基本實現(xiàn),所有Resource實現(xiàn)類都繼承了該類,所有繼承該類的Resource一般只需要實現(xiàn)以下方法即可:
public File getFile() throws IOException
public URL getURL() throws IOException
public String getDescription()
public InputStream getInputStream() throws IOException
該類默認實現(xiàn)中,將toString、equals、hashCode都代理給Description屬性;isReadable總是返回true,而isOpen總是返回false;exists方法實現(xiàn)中,先調(diào)用getFile返回的File對象的exists方法,如果失敗,查看是否可以獲得InputStream,如果可以,返回true,否則,返回false;getURL和getFile、createRelative方法拋出FileNotFoundException,而getURI則代理給getURL方法。
ByteArrayResource類
ByteArrayResource是一個簡單的Resource實現(xiàn),它是對二進制數(shù)組的封裝,每次調(diào)用getInputStream時都會以這個二進制數(shù)組作為源創(chuàng)建一個ByteArrayInputStream。它的exists方法總是返回true,而且重寫了equals、hashCode的方法,以判斷二進制數(shù)組的內(nèi)容;它的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類
FileSystemResource是對File的封裝,在構(gòu)建FileSystemResource時可以傳入File對象或路徑字符串(這里的路徑可以是相對路徑,相對路徑是相對于System.getProperty(“user.dir”)的值所在的路徑,也可以是絕對路徑,也可以是“file:”開頭的路徑值),在內(nèi)部會創(chuàng)建相應(yīng)的File對象,并且計算其path值,這里的path是計算完“.”和“..”影響的值(規(guī)格化)。
在getInputStream方法中,使用該File對象創(chuàng)建FileInputStream;而path值作為description屬性、equals、hashCode等方法的實現(xiàn);所有其他方法(exists、isReadable、getURL等)都代理給File對象;createRelative方法中使用path計算相對路徑,其算法是:找到最后一個路徑分隔符(/),將相對路徑添加到該分隔符之后,傳入的相對路徑可以是以路徑分割符(/)開頭,也可以不以分隔符(/)開頭,他們的效果是一樣的,對相對路徑存在的“.”和“..”會在創(chuàng)建FileSystemResource類時處理。最后,當(dāng)使用將一個目錄的File對象構(gòu)建FileSystemResource時,調(diào)用createRelative方法,其相對路徑的父目錄和當(dāng)前FileSystemResource的父目錄相同,比如使用”/home/Levin/dir1”目錄創(chuàng)建FileSystemResource對象,該Resource對象調(diào)用createRelative,并傳入”file”,那么出現(xiàn)的結(jié)果為”/home/Levin/file”,如果要得到”/home/Levin/dir1/file”,那么構(gòu)建FileSystemResource時,應(yīng)該傳入”/home/Levin/dir1/”字符串。
public boolean isReadable() {
return (this.file.canRead() && !this.file.isDirectory());
}
public InputStream getInputStream() throws IOException {
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類
UrlResource是對URL和URI的封裝。在構(gòu)建UrlResource時可以傳入URL、URI和Path字符串(帶協(xié)議字符串,如”file:”)。在UrlResource內(nèi)部還會創(chuàng)建一個cleanedUrl,它是規(guī)格化(計算“.”和“..”后的值),該URL將會用于equals、hashCode方法的實現(xiàn)。
在getInputStream方法實現(xiàn)中,它使用URL.openConnection()方法獲取URLConnection,后調(diào)用該URLConnection的getInputStream方法。對getFile()方法,只支持文件系統(tǒng)的資源,即URL字符串的協(xié)議部分為”file:”。UrlResource還支持從jar、zip、vfszip、wsjar等內(nèi)部文件,以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,然而這些內(nèi)部文件本身并沒有lastModified的屬性,因而對這些內(nèi)部文件,UrlResource將jar、zip等文件的lastModified視為這些內(nèi)部文件的lastModified屬性。對createRelative方法,直接使用URL提供的構(gòu)造函數(shù),忽略傳入的relativePath中的路徑分隔符“/”。
public InputStream getInputStream() throws IOException {
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));
}
ClassPathResource類
對classpath下資源的封裝,或者說是對ClassLoader.getResource()方法或Class.getResource()方法的封裝。它支持在當(dāng)前classpath中讀取資源文件。可以傳入相對classpath的文件全路徑名和ClassLoader構(gòu)建ClassPathResource,或忽略ClassLoader采用默認ClassLoader(即Thread Context ClassLoader),此時在getInputStream()方法實現(xiàn)時時會使用ClassLoader.getResourceAsStream()方法,由于使用ClassLoader獲取資源時默認相對于classpath的根目錄,因而構(gòu)造函數(shù)會忽略開頭的“/”字符。ClassPathResource還可以使用文件路徑和Class作為參數(shù)構(gòu)建,此時若文件路徑以“/”開頭,表示該文件為相對于classpath的絕對路徑,否則為相對Class實例的相對路徑,在getInputStream()方法實現(xiàn)時使用Class.getResourceAsStream()方法。
getFile()方法只支持存在于文件系統(tǒng)中的資源;對lastModified的屬性,若是jar、zip等文件中的資源,則采用jar、zip文件本身的lastModified屬性;equals會同時判斷path、classloader、clazz字段,而hashCode則只使用path。
public InputStream getInputStream() throws IOException {
InputStream is = null;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else {
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;
if (this.clazz != null) {
url = this.clazz.getResource(this.path);
}
else {
url = this.classLoader.getResource(this.path);
}
if (url == null) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
public File getFile() throws IOException {
return ResourceUtils.getFile(getURL(), getDescription());
}
protected File getFileForLastModifiedCheck() throws IOException {
URL url = getURL();
if (ResourceUtils.isJarURL(url)) {
URL actualUrl = ResourceUtils.extractJarFileURL(url);
return ResourceUtils.getFile(actualUrl);
}
else {
return ResourceUtils.getFile(url, getDescription());
}
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ClassPathResource(pathToUse, this.classLoader, this.clazz);
}
InputStreamResource類
InputStreamResource是對InputStream的封裝,它接收InputStream作為構(gòu)造函數(shù)參數(shù),它的isOpen總是返回true,并且只能被讀取一次(即getInputStream方法只能被調(diào)用一次),exists、isReadable方法也總是返回true。由于它不能被多次讀取,只有當(dāng)不用多次讀取的時候才使用該類,并且只有當(dāng)沒有其他可用Resource類時才使用該類。在Spring內(nèi)部貌似沒有使用它。它只實現(xiàn)了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類
DescriptiveResource是對非物理資源的Description的封裝。它實現(xiàn)了getDescription()方法。Resource中Description屬性主要用于錯誤處理時能更加準確的打印出錯位置的信息,DescriptiveResource提供對那些需要提供Resource接口中的Description屬性作為錯誤打印信息的方法自定義的描述信息。比如在BeanDefinitionReader中,在僅僅使用InputSource作為源加載BeanDefinition時,就可以使用DescriptiveResource定義自己的Description,從而在出錯信息中可以方便的知道問題源在哪里。
BeanDefinitionResource類
在Spring中Resource可以用于非物理資源的抽,BeanDefinitionResource是對BeanDefinition的封裝。BeanDefinitionResource類似DescriptiveResource,它也只實現(xiàn)了getDescription()方法,用于在解析某個BeanDefinition出錯時顯示錯誤源信息:
public String getDescription() {
return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription();
}
ContextResource接口
在Spring中還定義了ContextResource接口,繼承自Resource接口,只包含一個方法:
public interface ContextResource extends Resource {
String getPathWithinContext();
}
getPathWithContext()方法相對于Context的路徑,如ServletContext、PortletContext、classpath、FileSystem等,在Spring core中它有兩個實現(xiàn)類FileSystemContextResource、ClassPathContextResource,他們分別是FileSystemResourceLoader和DefaultResourceLoader中的內(nèi)部類,他們對getPathWithContext()方法的實現(xiàn)只是簡單的返回path值。
另外,在Spring Web模塊中,有一個ServletContextResource實現(xiàn)類,它使用ServletContext和path作為參數(shù)構(gòu)造,getInputStream、getURL、getURI、getFile等方法中將實現(xiàn)代理給ServletContext,其中getPathWithContext方法依然返回path字符串:
public boolean exists() {
try {
URL url = this.servletContext.getResource(this.path);
return (url != null);
}
catch (MalformedURLException ex) {
return false;
}
}
public InputStream getInputStream() throws IOException {
InputStream is = this.servletContext.getResourceAsStream(this.path);
if (is == null) {
throw new FileNotFoundException("Could not open " + getDescription());
}
return is;
}
public URL getURL() throws IOException {
URL url = this.servletContext.getResource(this.path);
if (url == null) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
public File getFile() throws IOException {
String realPath = WebUtils.getRealPath(this.servletContext, this.path);
return new File(realPath);
}
public Resource createRelative(String relativePath) {
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
return new ServletContextResource(this.servletContext, pathToUse);
}