您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么理解Spring中的Resource與ResourceLoader體系”,在日常操作中,相信很多人在怎么理解Spring中的Resource與ResourceLoader體系問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么理解Spring中的Resource與ResourceLoader體系”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
這些Resources主要就分為2大類:只讀,與可讀可寫。
從上面類圖我們可以看出FileSystemResource,實現(xiàn)了WritableResource,因此僅有這個類屬于可讀可寫,而其它的均屬于只讀的Resource.
Resources接口
public interface Resource extends InputStreamSource { // 判斷資源是否存在 boolean exists(); // 判斷資源是否可讀,只有在返回true的時候,getInputStream方法才可用 boolean isReadable(); // 判斷資源是否已打開,如果已打開則資源不能多次讀寫,資源應(yīng)該在讀完成之后關(guān)閉。 boolean isOpen(); // 獲取資源對象的URL,如果該資源不能表示為URL形式則拋出異常 URL getURL() throws IOException; // 獲取資源對象的URI,如果該資源不能表示為URI形式則拋出異常 URI getURI() throws IOException; // 獲取資源的File表示對象,如果資源不能表示為File對象則拋出異常 File getFile() throws IOException; // 獲取資源內(nèi)容的長度,如果資源無法解析則拋出異常 long contentLength() throws IOException; // 獲取資源最后修改時間戳,如果資源無法解析則拋出異常 long lastModified() throws IOException; // 相對當(dāng)前資源創(chuàng)建新的資源對象,如果相對的資源無法解析則拋出異常 Resource createRelative(String relativePath) throws IOException; // 獲取當(dāng)前資源的文件名,如果當(dāng)前資源沒有文件名則返回null String getFilename(); // 獲取當(dāng)對資源的描述信息 String getDescription(); }
在Resource接口中定義的方法,并不需要每一種實際資源類型都必須實現(xiàn),各個實際資源類型根據(jù)自身的情況決定要實現(xiàn)哪些方法。例如基于文件的資源一般會實現(xiàn)getFile方法,而不是基于文件的資源則一般不實現(xiàn)getFile方法。
對于大多數(shù)資源文件來說,不一定可寫但一般是可讀的。因此這里專門為可寫的資源類型定義了WritableResource接口,此接口中定義了兩個和寫操作相關(guān)的方法:
boolean isWritable(); OutputStream getOutputStream() throws IOException;
public abstract class AbstractResource implements Resource { public boolean exists() { // Try file existence: can we find the file in the file system? try { return getFile().exists(); } catch (IOException ex) { // Fall back to stream existence: can we open the stream? try { InputStream is = getInputStream(); is.close(); return true; } catch (Throwable isEx) { return false; } } } public boolean isReadable() { return true; } public boolean isOpen() { return false; } public URL getURL() throws IOException { // 默認(rèn)認(rèn)為資源無法表示為URL,子類可覆寫此方法 throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } public URI getURI() throws IOException { URL url = getURL(); try { // 通過getURL方法的返回值來進(jìn)行轉(zhuǎn)換 return ResourceUtils.toURI(url); } catch (URISyntaxException ex) { throw new NestedIOException("Invalid URI [" + url + "]", ex); } } public File getFile() throws IOException { // 默認(rèn)認(rèn)為資源無法表示為File對象,子類可覆寫 throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); } public long contentLength() throws IOException { InputStream is = this.getInputStream(); Assert.state(is != null, "resource input stream must not be null"); try { // 默認(rèn)實現(xiàn)為讀取inputStream中的所有數(shù)據(jù)來獲取長度 long size = 0; byte[] buf = new byte[255]; int read; while((read = is.read(buf)) != -1) { size += read; } return size; } finally { try { is.close(); } catch (IOException ex) { } } } public long lastModified() throws IOException { long lastModified = getFileForLastModifiedCheck().lastModified(); if (lastModified == 0L) { throw new FileNotFoundException(getDescription() + " cannot be resolved in the file system for resolving its last-modified timestamp"); } return lastModified; } protected File getFileForLastModifiedCheck() throws IOException { return getFile(); } public Resource createRelative(String relativePath) throws IOException { // 默認(rèn)不支持創(chuàng)建相對路徑資源 throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); } public String getFilename() { return null; // 默認(rèn)返回null,即認(rèn)為資源五文件名 } @Override public String toString() { return getDescription(); } @Override public boolean equals(Object obj) { return (obj == this || (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription()))); } @Override public int hashCode() { return getDescription().hashCode(); } }
在AbstractResource的實現(xiàn)中,默認(rèn)認(rèn)為資源不能夠表示為URL和File的形式,這樣的資源如ByteArrayResource、InputStreamResource等都可以適應(yīng),因為這些資源類型底層并不是基于文件而是包裝了字節(jié)數(shù)組或輸入流而成,因此正對這種類型的操作,一般只支持讀取操作。
從上面的繼承關(guān)系圖中可以看到,F(xiàn)ileSystemResource不但繼承了AbstractResource還實現(xiàn)了WritableResource接口,也就是基于文件系統(tǒng)的資源類型,一般可以支持讀寫操作,當(dāng)然一般也會支持相對路徑資源。
abstractFileResolvingResource表示需要通過解析URL來獲取的資源。例如其exists方法的實現(xiàn):
@Override public boolean exists() { try { URL url = getURL(); if (ResourceUtils.isFileURL(url)) { // Proceed with file system resolution return getFile().exists();// 如果是文件,則直接檢測文件是否存在 } else { // Try a URL connection content-length header URLConnection con = url.openConnection(); customizeConnection(con); HttpURLConnection httpCon = (con instanceof HttpURLConnection ? (HttpURLConnection) con : null); if (httpCon != null) { // 如果是http url則檢測url對應(yīng)的資源是否存在 int code = httpCon.getResponseCode(); if (code == HttpURLConnection.HTTP_OK) { return true; } else if (code == HttpURLConnection.HTTP_NOT_FOUND) { return false; } } if (con.getContentLength() >= 0) { return true; } if (httpCon != null) { // No HTTP OK status, and no content-length header: give up httpCon.disconnect(); return false; } else { // Fall back to stream existence: can we open the stream? getInputStream().close(); return true; } } } catch (IOException ex) { return false; } }
并且他還有兩個子類:UrlResource和ClassPathResouce,其中ClassPathResource類型是我們在Spring中非常常用的資源類型。
UrlResource來說,基本上就是通過解析URL來完成相關(guān)的操作,只要符合URL規(guī)范的格式都可以表示為UrlResource對象。
public class ClassPathResource extends AbstractFileResolvingResource { private final String path; private ClassLoader classLoader; private Class<?> clazz; public ClassPathResource(String path) { this(path, (ClassLoader) null); } public ClassPathResource(String path, ClassLoader classLoader) { Assert.notNull(path, "Path must not be null"); String pathToUse = StringUtils.cleanPath(path); if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } this.path = pathToUse; // 如果ClassLoader為null則使用默認(rèn)的ClassLoader this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader()); } public ClassPathResource(String path, Class<?> clazz) { Assert.notNull(path, "Path must not be null"); this.path = StringUtils.cleanPath(path); this.clazz = clazz;// 使用Class來加載資源,也可以使用ClassLoader加載 } protected ClassPathResource(String path, ClassLoader classLoader, Class<?> clazz) { this.path = StringUtils.cleanPath(path); // 同時使用Clss和ClassLoader this.classLoader = classLoader; this.clazz = clazz; } public final String getPath() { return this.path; } public final ClassLoader getClassLoader() { return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader); } @Override public boolean exists() { return (resolveURL() != null); } protected URL resolveURL() { // 資源是否存在通過Class和ClassLoader來判斷 if (this.clazz != null) { return this.clazz.getResource(this.path); } else if (this.classLoader != null) { return this.classLoader.getResource(this.path); } else { return ClassLoader.getSystemResource(this.path); } @Override public InputStream getInputStream() throws IOException { InputStream is; // InputStream的獲取也是通過Class和ClassLoader來判斷 if (this.clazz != null) { is = this.clazz.getResourceAsStream(this.path); } else if (this.classLoader != null) { is = this.classLoader.getResourceAsStream(this.path); } else { is = ClassLoader.getSystemResourceAsStream(this.path); } if (is == null) { throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist"); } return is; } @Override public URL getURL() throws IOException { URL url = resolveURL(); if (url == null) { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist"); } return url; } @Override public Resource createRelative(String relativePath) { String pathToUse = StringUtils.applyRelativePath(this.path, relativePath); return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) : new ClassPathResource(pathToUse, this.classLoader)); } @Override public String getFilename() { return StringUtils.getFilename(this.path); } @Override public String getDescription() { StringBuilder builder = new StringBuilder("class path resource ["); String pathToUse = path; if (this.clazz != null && !pathToUse.startsWith("/")) { builder.append(ClassUtils.classPackageAsResourcePath(this.clazz)); builder.append('/'); } if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } builder.append(pathToUse); builder.append(']'); return builder.toString(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof ClassPathResource) { ClassPathResource otherRes = (ClassPathResource) obj; return (this.path.equals(otherRes.path) && ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) && ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz)); } return false; } @Override public int hashCode() { return this.path.hashCode(); } }
雖然ClassPathContextResource和ClassPathRelaticeContextResource都是繼承自ClassPathResource,但是前者使用的ClassLoader來加載資源,后者使用的是Class來加載資源,二者還是有區(qū)別的:
ClassLoader來加載資源,路徑以類路徑的根目錄為基準(zhǔn)(如WEB-INF/classes為基準(zhǔn))。
Class來架子資源,資源以Class縮在的包路徑為基準(zhǔn)(如Web-INF/classes/com/test/resource/)
Spring中對資源進(jìn)行抽象,從而統(tǒng)一對資源操作的API,屏蔽不同資源之間的差異。使得其他組件可以不關(guān)心具體資源類型的實現(xiàn),使用統(tǒng)一的API進(jìn)行操作,并且通過不同的接口來分別定義資源的不同行為,然后通過抽象類的形式給出一個通用的實現(xiàn),底層具體的實現(xiàn)只需要繼承這個抽象類,并覆寫跟當(dāng)前資源類型需要特殊處理的方法即可。另外,在定義接口時,通過給出一對方法(如:isReadable和getInputStream)來分離條件檢測和執(zhí)行邏輯。
Spring中的Resource體系,只是對資源類型進(jìn)行了抽象,統(tǒng)一了接口,但是如果需要使用不同類型的Resource的時候,還是得通過new具體資源類型的方式來獲取。Spring中為了簡化對Resource的查找和加載,提供了ResourceLoader來專門負(fù)責(zé)加載Resource,使用這不需要關(guān)心如何加載具體的資源,只需要給出資源的定義(schame),剩下的就交由ResourceLoader來處理了。
ResourceLoader接口:
public interface ResourceLoader { // ClassPathResource對應(yīng)的Url的協(xié)議頭(前綴) String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // 根據(jù)給出的資源Url獲取對應(yīng)的Resource對象 Resource getResource(String location); // 獲取當(dāng)前ResourceLoader所使用的ClassLoader ClassLoader getClassLoader(); }
從接口定義中看ResourceLoader最主要的行為就是getResource操作。getResource方法接收一個字符串類型參數(shù),通過對字符串參數(shù)的解析和處理返回對應(yīng)的Resource對象,這里的location參數(shù)可以包含一定的協(xié)議頭或前綴來表明參數(shù)的來源信息。
默認(rèn)的實現(xiàn)DefaultResourceLoader,它可以使用ClassLoader作為參數(shù)進(jìn)行創(chuàng)建。其getResource方法實現(xiàn)如下:
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith(CLASSPATH_URL_PREFIX)) { // classpath:前綴 // ClassPathResource需要使用Class或ClassLoader,這里傳入ClassLoader return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); // 如果不符合URL規(guī)范,則當(dāng)做普通路徑(如:test/resource.xml)處理 } } }
對于傳入的location參數(shù),顯示判斷了是否包含classpath:前綴,如果包含則返回ClassPathResource對象,如果不是則通過解析URL的方式解析location參數(shù),如果參數(shù)符合URL規(guī)范,則創(chuàng)建一個UrlResource,如果資源既不包含classpath:特殊前綴,也不是URL形式,那么就將其當(dāng)做普通的資源路徑傳遞給getResourceByPath進(jìn)行處理:
protected Resource getResourceByPath(String path) { return new ClassPathContextResource(path, getClassLoader()); } private static class ClassPathContextResource extends ClassPathResource implements ContextResource { public ClassPathContextResource(String path, ClassLoader classLoader) { super(path, classLoader); } public String getPathWithinContext() { return getPath(); } @Override public Resource createRelative(String relativePath) { String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath); return new ClassPathContextResource(pathToUse, getClassLoader()); } }
getResourceByPath返回ClassPathContextResource類型,實際上也可以也就是ClassPathResource??梢钥闯觯琑esourceLoader中并不判斷資源是否真實存在和是否可讀寫,而僅僅通過解析出傳入的location參數(shù)返回不同的Resource實現(xiàn)而已,資源是否存在,是否可讀,需要調(diào)用方在拿到Resource對象后通過Resource提供的方法自行判斷。
Spring除了提供ResourceLoader之外,還提供了ResourcePatternResolver來擴(kuò)充ResourceLoader。引進(jìn)了一個新的前綴:classpath*:。和classpath:的差別就是,classpath*:可以搜索class path下所有滿足條件的資源(包括同名的資源),而classpath:則只能返回一個資源(即使存在多個)。
到此,關(guān)于“怎么理解Spring中的Resource與ResourceLoader體系”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。