Spring-資源加載

在 Java 中,將不同來源的資源抽象成 URL,通過註冊不同的 handler( URLStreamHandler) 來處理不同來源的資源的讀取邏輯。

然而 URL沒有默認定義相對 Classpath 或 ServletContext 等資源的 handler ,雖然可以註冊自己的 URLStreamHandler 來解析特定的 URL 前綴(協議)。但是 URL 也沒有提供基本的方法、如檢查當前資源是否存在,檢查資源是否存在等方法。

URL: 我可以加載各種的資源…XXX

Spring: 你是個好人

Spring 實現了自己的資源加載策略

  • 職能劃分清楚。資源的定義和資源的加載有明確的界限
  • 統一的抽象,統一的資源定義和資源加載策略。

統一資源

org.springframework.core.io.Resource接口抽象了所有 Spring 內部使用到的底層資源,如 File、URL、Classpath。

public interface Resource extends InputStreamSource {
	.......
	.......
}

org.springframework.core.io.InputStreamSource封裝任何能返回 InputStream 的類、如 File、Classpath 下的資源和 ByteArray,該接口只有一個方法 getInputStream

public interface InputStreamSource {

	/**
	 * 表示任意形式的資源都可以被轉換成輸入流、最好每一次調用這個方法的時候都是返回一個新的InputStream
	 * 看子類{@link FileSystemResource}符合這個要求
	 */
	InputStream getInputStream() throws IOException;

}

Resource 接口提供了比 URL 更加多和便捷的方法

idea 截圖

對於不同來源的資源文件都有相應的 Resource 實現

  • 文件 FileSystemResource
  • classpath 資源 ClassPathResource
  • url 資源 URLResource
  • InputStream 資源 InputStreamResource
  • byte 數組資源 ByteArrayResource

idea 類圖

其中 AbstractResource 實現了 Resource 接口中大多數的方法,如果我們要自定義 Resource ,可以繼承這個類,而不是直接實現 Resource

idea 截圖

統一資源加載

org.springframework.core.io.ResourceLoader是 Spring 資源加載的抽象

public interface ResourceLoader {
	/**
	 * Pseudo URL prefix for loading from the class path: "classpath:".
	 */
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
	/**
	 * 根據 資源路徑 返回一個Resource 句柄、但不確保這個Resource 一定存在、需要調用 Resource exists 方法         		* 判斷
	 * URL位置資源,如”file:C:/test.dat”
	 * ClassPath位置資源,如”classpath:test.dat”
	 * 相對路徑資源,如”WEB-INF/test.dat”,此時返回的Resource實例根據實現不同而不同
	 *
	 * 要可重用、可多次調用 getInputStream
	 */
	Resource getResource(String location);
	@Nullable
	ClassLoader getClassLoader();

}

idea 類圖

主要實現類就是 DefaultResourceLoader,其中最核心的方法就是 getResource

@Override
		public Resource getResource(String location) {
			Assert.notNull(location, "Location must not be null");

			// 協議解釋
			for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
				Resource resource = protocolResolver.resolve(location, this);
				if (resource != null) {
					return resource;
				}
			}

		// 如果以/ 開頭、則創建 ClassPathContextResource、其實也是一個 ClassPathResource 是他的子類、 類路徑的資源
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		} else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			// 如果以 classpath 開頭 則 創建ClassPathResource
			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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			} catch (MalformedURLException ex) {
				// No URL -> resolve as resource path.
				return getResourceByPath(location);
			}
		}
	}

其中 ProtocolResolver

@FunctionalInterface
public interface ProtocolResolver {

	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);
}

主要用於處理用戶自定義資源協議的,我們可以通過實現此接口來解釋我們自定義的資源協議,只需要將實現類對象添加到 DefaultResourceLoader

    public void addProtocolResolver(ProtocolResolver resolver) {
        Assert.notNull(resolver, "ProtocolResolver must not be null");
        this.protocolResolvers.add(resolver);
    }

ResourcePatternResolver

在 ResourceLoader 接口的基礎上增加了 Resource[] getResources(String locationPattern)方法,支持根據指定的路徑匹配模式每次返回多個 Resource 實例。

@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		// classpath*:
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// a class path resource (multiple resources for same name possible)
			// 路徑中包含通配符
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// a class path resource pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// all class path resources with the given name
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		else {
			// Generally only look for a pattern after a prefix here,
			// and on Tomcat only after the "*/" separator for its "war:" protocol.
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			else {
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

涉及到的設計模式

面試的時候

面試官: 看過什麼框架的源碼嗎

菜雞的我: 我看過 Spring 的源碼

面試官: 那你說說 Spring 中用到了什麼設計模式

菜雞的我: 額…好像…有…我忘記了

責任鏈模式

在資源定義和資源加載這一塊中,我們可以看到在 DefaultResourceLoader 中的 ProtocolResolver,

private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

public Resource getResource(String location) {
			Assert.notNull(location, "Location must not be null");

			// 協議解釋
			for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
				Resource resource = protocolResolver.resolve(location, this);
				if (resource != null) {
					return resource;
				}
			}
      .....
      .....

一個加載資源的請求,會被其中一個 ProtocolResolver 去解釋成一個 Resource 或者沒有一個 ProtocolResolver 能處理這個請求並返回一個 Resource (最終被後面的默認解析成Resource)。這一個過程其實算是使用到了責任鏈模式的,只是不是一個純正的責任鏈模式。

圖片來源於網絡

閻宏博士的《JAVA與模式》一書中是這樣描述責任鏈(Chain of Responsibility)模式的

責任鏈模式是一種對象的行爲模式。在責任鏈模式裏,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。

純與不純的責任鏈模式

  • 純的責任鏈模式: 要麼處理這個請求、要麼完全不處理這個請求並將這個請求傳遞給下一個處理器;不純的責任鏈模式:某個處理器處理了一部分這個請求的業務,然後又將剩下的傳遞給下一個處理器讓其繼續處理
  • 純的責任鏈模式: 一個請求必須被一個請求處理器所處理;不純的責任鏈模式:這個請求可能沒有被任何一個處理器處理

而對於怎麼存儲下一個 handler 的引用,當然可以在當前 handler 中存有下一個 handler 的引用,但是更加常用的還是使用數組或者列表將所有 handler 存儲起來,然後進行鏈式調用處理請求,如 servlet 的filter即使如此。

策略模式

不同的 ResourceLoader 對應着不同的資源加載策略

idea 類圖

org.springframework.context.support.GenericApplicationContext類中

public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {

	private final DefaultListableBeanFactory beanFactory;

	@Nullable
	private ResourceLoader resourceLoader;
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	@Override
	public Resource getResource(String location) {
		if (this.resourceLoader != null) {
			return this.resourceLoader.getResource(location);
		}
    // 最終調用 DefaultResourceLoader 的 getResource
		return super.getResource(location);
	}

這個不就是策略模式嗎

圖片來源自維基百科

相關文章

Spring 專輯

這次一定?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章