在 Java 中,將不一樣來源的資源抽象成 URL
,經過註冊不一樣的 handler
( URLStreamHandler
) 來處理不一樣來源的資源的讀取邏輯。web
然而 URL
沒有默認定義相對 Classpath 或 ServletContext 等資源的 handler ,雖然能夠註冊本身的 URLStreamHandler 來解析特定的 URL 前綴(協議)。可是 URL 也沒有提供基本的方法、如檢查當前資源是否存在,檢查資源是否存在等方法。面試
URL: 我能夠加載各類的資源.......XXXspring
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,該接口只有一個方法 getInputStreamapp
public interface InputStreamSource {
/** * 表示任意形式的資源均可以被轉換成輸入流、最好每一次調用這個方法的時候都是返回一個新的InputStream * 看子類{@link FileSystemResource}符合這個要求 */ InputStream getInputStream() throws IOException; } 複製代碼
Resource 接口提供了比 URL 更加多和便捷的方法框架
對於不一樣來源的資源文件都有相應的 Resource 實現編輯器
其中 AbstractResource 實現了 Resource 接口中大多數的方法,若是咱們要自定義 Resource ,能夠繼承這個類,而不是直接實現 Resourceide
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(); } 複製代碼
主要實現類就是 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); } 複製代碼
在 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 對應着不一樣的資源加載策略
在 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); } 複製代碼
這個不就是策略模式嗎
相關文章
本文使用 mdnice 排版