溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么理解Spring中的Resource與ResourceLoader體系

發(fā)布時間:2021-11-16 11:14:07 來源:億速云 閱讀:153 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要介紹“怎么理解Spring中的Resource與ResourceLoader體系”,在日常操作中,相信很多人在怎么理解Spring中的Resource與ResourceLoader體系問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么理解Spring中的Resource與ResourceLoader體系”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

一 Resources體系

怎么理解Spring中的Resource與ResourceLoader體系
這些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方法。

1.1 WriteableResource接口

對于大多數(shù)資源文件來說,不一定可寫但一般是可讀的。因此這里專門為可寫的資源類型定義了WritableResource接口,此接口中定義了兩個和寫操作相關(guān)的方法:

	boolean isWritable();
	OutputStream getOutputStream() throws IOException;

1.2 AbstractResource類

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ù)組或輸入流而成,因此正對這種類型的操作,一般只支持讀取操作。

1.2.1 FileSystemResource類

從上面的繼承關(guān)系圖中可以看到,F(xiàn)ileSystemResource不但繼承了AbstractResource還實現(xiàn)了WritableResource接口,也就是基于文件系統(tǒng)的資源類型,一般可以支持讀寫操作,當(dāng)然一般也會支持相對路徑資源。

1.2.2 AbstractFileResolvingResource類

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中非常常用的資源類型。

(1)UrlResource

UrlResource來說,基本上就是通過解析URL來完成相關(guān)的操作,只要符合URL規(guī)范的格式都可以表示為UrlResource對象。

(2)ClassPathResouce
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/)

1.3 總結(jié)

Spring中對資源進(jìn)行抽象,從而統(tǒng)一對資源操作的API,屏蔽不同資源之間的差異。使得其他組件可以不關(guān)心具體資源類型的實現(xiàn),使用統(tǒng)一的API進(jìn)行操作,并且通過不同的接口來分別定義資源的不同行為,然后通過抽象類的形式給出一個通用的實現(xiàn),底層具體的實現(xiàn)只需要繼承這個抽象類,并覆寫跟當(dāng)前資源類型需要特殊處理的方法即可。另外,在定義接口時,通過給出一對方法(如:isReadable和getInputStream)來分離條件檢測和執(zhí)行邏輯。

二 ResourceLoader體系

怎么理解Spring中的Resource與ResourceLoader體系
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ù)的來源信息。

2.1 DefaultResourceLoader類

默認(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提供的方法自行判斷。

2.2 ResourcePatternResolver

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>

向AI問一下細(xì)節(jié)

免責(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)容。

AI