您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么理解Spring中的Resource與ResourceLoader體系”,在日常操作中,相信很多人在怎么理解Spring中的Resource與ResourceLoader體系問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么理解Spring中的Resource與ResourceLoader體系”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
這些Resources主要就分為2大類:只讀,與可讀可寫。
從上面類圖我們可以看出FileSystemResource,實現了WritableResource,因此僅有這個類屬于可讀可寫,而其它的均屬于只讀的Resource.
Resources接口
public interface Resource extends InputStreamSource { // 判斷資源是否存在 boolean exists(); // 判斷資源是否可讀,只有在返回true的時候,getInputStream方法才可用 boolean isReadable(); // 判斷資源是否已打開,如果已打開則資源不能多次讀寫,資源應該在讀完成之后關閉。 boolean isOpen(); // 獲取資源對象的URL,如果該資源不能表示為URL形式則拋出異常 URL getURL() throws IOException; // 獲取資源對象的URI,如果該資源不能表示為URI形式則拋出異常 URI getURI() throws IOException; // 獲取資源的File表示對象,如果資源不能表示為File對象則拋出異常 File getFile() throws IOException; // 獲取資源內容的長度,如果資源無法解析則拋出異常 long contentLength() throws IOException; // 獲取資源最后修改時間戳,如果資源無法解析則拋出異常 long lastModified() throws IOException; // 相對當前資源創建新的資源對象,如果相對的資源無法解析則拋出異常 Resource createRelative(String relativePath) throws IOException; // 獲取當前資源的文件名,如果當前資源沒有文件名則返回null String getFilename(); // 獲取當對資源的描述信息 String getDescription(); }
在Resource接口中定義的方法,并不需要每一種實際資源類型都必須實現,各個實際資源類型根據自身的情況決定要實現哪些方法。例如基于文件的資源一般會實現getFile方法,而不是基于文件的資源則一般不實現getFile方法。
對于大多數資源文件來說,不一定可寫但一般是可讀的。因此這里專門為可寫的資源類型定義了WritableResource接口,此接口中定義了兩個和寫操作相關的方法:
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 { // 默認認為資源無法表示為URL,子類可覆寫此方法 throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } public URI getURI() throws IOException { URL url = getURL(); try { // 通過getURL方法的返回值來進行轉換 return ResourceUtils.toURI(url); } catch (URISyntaxException ex) { throw new NestedIOException("Invalid URI [" + url + "]", ex); } } public File getFile() throws IOException { // 默認認為資源無法表示為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 { // 默認實現為讀取inputStream中的所有數據來獲取長度 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 { // 默認不支持創建相對路徑資源 throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); } public String getFilename() { return null; // 默認返回null,即認為資源五文件名 } @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的實現中,默認認為資源不能夠表示為URL和File的形式,這樣的資源如ByteArrayResource、InputStreamResource等都可以適應,因為這些資源類型底層并不是基于文件而是包裝了字節數組或輸入流而成,因此正對這種類型的操作,一般只支持讀取操作。
從上面的繼承關系圖中可以看到,FileSystemResource不但繼承了AbstractResource還實現了WritableResource接口,也就是基于文件系統的資源類型,一般可以支持讀寫操作,當然一般也會支持相對路徑資源。
abstractFileResolvingResource表示需要通過解析URL來獲取的資源。例如其exists方法的實現:
@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對應的資源是否存在 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來完成相關的操作,只要符合URL規范的格式都可以表示為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則使用默認的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來加載資源,二者還是有區別的:
ClassLoader來加載資源,路徑以類路徑的根目錄為基準(如WEB-INF/classes為基準)。
Class來架子資源,資源以Class縮在的包路徑為基準(如Web-INF/classes/com/test/resource/)
Spring中對資源進行抽象,從而統一對資源操作的API,屏蔽不同資源之間的差異。使得其他組件可以不關心具體資源類型的實現,使用統一的API進行操作,并且通過不同的接口來分別定義資源的不同行為,然后通過抽象類的形式給出一個通用的實現,底層具體的實現只需要繼承這個抽象類,并覆寫跟當前資源類型需要特殊處理的方法即可。另外,在定義接口時,通過給出一對方法(如:isReadable和getInputStream)來分離條件檢測和執行邏輯。
Spring中的Resource體系,只是對資源類型進行了抽象,統一了接口,但是如果需要使用不同類型的Resource的時候,還是得通過new具體資源類型的方式來獲取。Spring中為了簡化對Resource的查找和加載,提供了ResourceLoader來專門負責加載Resource,使用這不需要關心如何加載具體的資源,只需要給出資源的定義(schame),剩下的就交由ResourceLoader來處理了。
ResourceLoader接口:
public interface ResourceLoader { // ClassPathResource對應的Url的協議頭(前綴) String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // 根據給出的資源Url獲取對應的Resource對象 Resource getResource(String location); // 獲取當前ResourceLoader所使用的ClassLoader ClassLoader getClassLoader(); }
從接口定義中看ResourceLoader最主要的行為就是getResource操作。getResource方法接收一個字符串類型參數,通過對字符串參數的解析和處理返回對應的Resource對象,這里的location參數可以包含一定的協議頭或前綴來表明參數的來源信息。
默認的實現DefaultResourceLoader,它可以使用ClassLoader作為參數進行創建。其getResource方法實現如下:
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規范,則當做普通路徑(如:test/resource.xml)處理 } } }
對于傳入的location參數,顯示判斷了是否包含classpath:前綴,如果包含則返回ClassPathResource對象,如果不是則通過解析URL的方式解析location參數,如果參數符合URL規范,則創建一個UrlResource,如果資源既不包含classpath:特殊前綴,也不是URL形式,那么就將其當做普通的資源路徑傳遞給getResourceByPath進行處理:
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。可以看出,ResourceLoader中并不判斷資源是否真實存在和是否可讀寫,而僅僅通過解析出傳入的location參數返回不同的Resource實現而已,資源是否存在,是否可讀,需要調用方在拿到Resource對象后通過Resource提供的方法自行判斷。
Spring除了提供ResourceLoader之外,還提供了ResourcePatternResolver來擴充ResourceLoader。引進了一個新的前綴:classpath*:。和classpath:的差別就是,classpath*:可以搜索class path下所有滿足條件的資源(包括同名的資源),而classpath:則只能返回一個資源(即使存在多個)。
到此,關于“怎么理解Spring中的Resource與ResourceLoader體系”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。