Spring IOC 源碼學習 2 加載

1 資源定義

  • spring 爲咱們提供了資源的抽象和和訪問接口 org.springframework.core.io.Resource, Resource繼承了 org.springframework.core.io.InputStreamSource 接口,同時由子類 AbstractResource提供默認的實現方法

接口定義以下java

/* 
 * @see #getInputStream()
 * @see #getURL()
 * @see #getURI()
 * @see #getFile()
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see FileUrlResource
 * @see FileSystemResource
 * @see ClassPathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */
public interface Resource extends InputStreamSource {

	/**
	 * 是否存在
	 */
	boolean exists();

	/**
	 * 是否可讀
	 */
	default boolean isReadable() {
		return exists();
	}

	/**
	 * 資源所表明的句柄是否被一個 stream 打開了
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * 是不是文件格式
	 */
	default boolean isFile() {
		return false;
	}

	
	URL getURL() throws IOException;

	
	URI getURI() throws IOException;


	File getFile() throws IOException;

	
	default ReadableByteChannel readableChannel() throws IOException{
		return Channels.newChannel(getInputStream());
	}

	
	long contentLength() throws IOException;

	
	long lastModified() throws IOException;


	Resource createRelative(String relativePath) throws IOException;

	
	@Nullable
	String getFilename();

	
	String getDescription();
複製代碼

類圖以下 spring

其中express

  • FileSystemResource 和文件資源打交道 而且在Spring 5.0之後 使用 NIO2 API進行讀/寫交互。
  • ByteArrayResource 對字節數組提供的數據的封裝
  • UrlResource 對 java.net.URL類型資源的封裝
  • ClassPathResource class path 類型資源的實現。使用給定的 ClassLoader 或者給定的 Class 來加載資源
  • InputStreamResource 將給定的 InputStream 做爲一種資源的 Resource 的實現類。

2 AbstractResource

public abstract class AbstractResource implements Resource {

	/**
	 * 判斷文件是否存在,若判斷過程產生異常,就關閉對應的流
	 */
	@Override
	public boolean exists() {
        //判斷文件是否存在
		try {
			return getFile().exists();
		}
		catch (IOException ex) {
			// 基於 InputStream 進行判斷
			try {
				getInputStream().close();
				return true;
			}
			catch (Throwable isEx) {
				return false;
			}
		}
	}

	/**
	 * 默承認讀
	 */
	@Override
	public boolean isReadable() {
		return exists();
	}

	/**
	 * 直接返回 false,表示未被打開
	 */
	@Override
	public boolean isOpen() {
		return false;
	}

	/**
	 * 直接返回False 表示不是一個文件
	 */
	@Override
	public boolean isFile() {
		return false;
	}

	/**
	 * 直接拋出異常,交給子類去實現
	 */
	@Override
	public URL getURL() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/**
	 * 基於 getURL() 返回的 URL 構建 URI
	 */
	@Override
	public URI getURI() throws IOException {
		URL url = getURL();
		try {
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
			throw new NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	/**
	 * 拋出異常 交於子類實現
	 */
	@Override
	public File getFile() throws IOException {
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	/**
	 * 根據 getInputStream() 的返回結果構建 ReadableByteChannel
	 */
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 資源的字節長度,經過所有讀取一遍來判斷
	 */
	@Override
	public long contentLength() throws IOException {
		InputStream is = getInputStream();
		try {
			long size = 0;
			byte[] buf = new byte[256];
			int read;
			while ((read = is.read(buf)) != -1) {
				size += read;
			}
			return size;
		}
		finally {
			try {
				is.close();
			}
			catch (IOException ex) {
			}
		}
	}

	/**
	 * 上次修改時間
	 */
	@Override
	public long lastModified() throws IOException {
		File fileToCheck = getFileForLastModifiedCheck();
		long lastModified = fileToCheck.lastModified();
		if (lastModified == 0L && !fileToCheck.exists()) {
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		return lastModified;
	}

	/**
	 * 
	 */
	protected File getFileForLastModifiedCheck() throws IOException {
		return getFile();
	}

	/**
	 * 
	 */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/**
	 * 
	 */
	@Override
	@Nullable
	public String getFilename() {
		return null;
	}


	/**
	 * 
	 */
	@Override
	public boolean equals(Object other) {
		return (this == other || (other instanceof Resource &&
				((Resource) other).getDescription().equals(getDescription())));
	}

	/**
	 * This implementation returns the description's hash code. * @see #getDescription() */ @Override public int hashCode() { return getDescription().hashCode(); } /** * */ @Override public String toString() { return getDescription(); } 複製代碼
  • 若是咱們想要實現自定義的Resource,只需繼承AbstractResource 便可

3 統一資源定位 ResourceLoader

Spring將資源的定位和加載進行了隔離數組

  • Resource 對資源進行了統一的定義
  • ResourceLoader 對資源加載進行統一的定義

ResourceLoader源碼bash

String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; //默認  classpath:


	Resource getResource(String location);

	
	@Nullable
	ClassLoader getClassLoader();
複製代碼
  • getResource 根據提供的參數location 返回Resource 對象,須要提早使用Resource.exist() 來判斷對象是否存在。支持多種資源加載
    • URL位置資源,如 "file:C:/test.xml"
    • ClassPath位置資源,如 "classpath:test.xml
    • 相對路徑資源,如 "WEB-INF/test.dat" ,此時返回的Resource 實例,根據實現不一樣而不一樣
  • getClassLoader 返回 ClassLoader 實例

3.1 子類實現

類圖以下ide

3.1.1

DefaultResourceLoader 是 ResourceLoader 的默認實現函數

public class DefaultResourceLoader implements ResourceLoader {

    @Nullable
    private ClassLoader classLoader;

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

    private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);
	
    @Nullable
    private ClassLoader classLoader;

    public DefaultResourceLoader() { // 無參構造函數
    	this.classLoader = ClassUtils.getDefaultClassLoader();
    }
    
    public DefaultResourceLoader(@Nullable ClassLoader classLoader) { // 帶 ClassLoader 參數的構造函數
    	this.classLoader = classLoader;
    }
    
    public void setClassLoader(@Nullable ClassLoader classLoader) {
    	this.classLoader = classLoader;
    }
    
    public void addProtocolResolver(ProtocolResolver resolver) {
    	Assert.notNull(resolver, "ProtocolResolver must not be null");
    	this.protocolResolvers.add(resolver);
    }

	
	
    public Collection<ProtocolResolver> getProtocolResolvers() {
        return this.protocolResolvers;
    }

    @Override
    @Nullable
    public ClassLoader getClassLoader() {
    	return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
    }
}
複製代碼

3.1.1.1 構造函數

  • 無參構造函數,使用默認的 ClassLoader 爲默認的
    • Thread.currentThread().getContextClassLoader() *在使用無參構造函數的時候能夠使用 ClassUtils.getDefaultClassLoader()
  • 有參構造函數 帶 ClassLoader 參數的構造函數

3.1.1.2 getResource

  • ResourceLoader 中最核心的方法爲 getResource(String location) ,它根據提供的 location 返回相應的 Resource 。而 DefaultResourceLoader 對該方法提供了核心實現(由於,它的兩個子類都沒有提供覆蓋該方法,因此能夠判定 ResourceLoader 的資源加載策略就封裝在 DefaultResourceLoader 中)
@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");

    // 首先,經過 ProtocolResolver 來加載資源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    // 其次,以 / 開頭,返回 ClassPathContextResource 類型的資源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    // 再次,以 classpath: 開頭,返回 ClassPathResource 類型的資源
    } else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    // 而後,根據是否爲文件 URL ,是則返回 FileUrlResource 類型的資源,不然返回 UrlResource 類型的資源
    } 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) {
            // 最後,返回 ClassPathContextResource 類型的資源
            
            return getResourceByPath(location);
        }
    }
}
複製代碼
  • 首先,經過 ProtocolResolver 來加載資源,成功返回 Resource 。
  • 其次,若 location 以 "/" 開頭,則調用 #getResourceByPath() 方法,構造 ClassPathContextResource 類型資源並返回。代碼以下:
protected Resource getResourceByPath(String path) {
	return new ClassPathContextResource(path, getClassLoader());
}
複製代碼
  • 若 location 以 "classpath:" 開頭,則構造 ClassPathResource 類型資源並返回。在構造該資源時,經過 getClassLoader() 獲取當前的 ClassLoader
  • 而後,構造 URL ,嘗試經過它進行資源定位,若沒有拋出 MalformedURLException 異常,則判斷是否爲 FileURL , 若是是則構造 FileUrlResource 類型的資源,不然構造 UrlResource 類型的資源。
  • 若在加載過程當中拋出 MalformedURLException 異常,則委派 DefaultResourceLoader.getResourceByPath() 方法,實現資源定位加載

3.1.2 ProtocolResolver

代碼路徑 org.springframework.core.io.ProtocolResolver,做爲 DefaultResourceLoader, 實現自定義 Resource,無需集成AbstractResource,只需實現ProtocolResolver接口便可gradle

@FunctionalInterface
public interface ProtocolResolver {

	/**
	 * 使用指定的 ResourceLoader ,解析指定的 location  若成功,則返回對應的 Resource
	 */
	@Nullable
	Resource resolve(String location, ResourceLoader resourceLoader);

}
複製代碼

須要用戶自定義它的實現類,而後調用DefaultResourceLoader.addProtocolResolver(ProtocolResolver resolver)方法便可ui

#3.2 FileSystemResourceLoader FileSystemResourceLoader 繼承自DefaultResourceLoader 並覆寫了 getResourceByPath(String locaition) 方法this

@Override
	protected Resource getResourceByPath(String path) {
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		return new FileSystemContextResource(path);
	}


	/**
	 * FileSystemResource that explicitly expresses a context-relative path
	 * through implementing the ContextResource interface.
	 */
	private static class FileSystemContextResource extends FileSystemResource implements ContextResource {

	    public FileSystemContextResource(String path) {
	    	super(path);
	    }

	    @Override
	    public String getPathWithinContext() {
        	return getPath();
	    }
	}
複製代碼

##3.2.1 FileSystemContextResource 繼承自FileSystemResource 實現 ContextResource

  • 構造方法 調用 FileSystemResource 的構造函數來構造 FileSystemResource

##3.2.2 示例

public static void main(String[] args) {
        ResourceLoader resourceLoader = new DefaultResourceLoader();

        Resource fileResource1 = resourceLoader.getResource("D:/Users/cindy/code/demo/build.gradle");
        System.out.println("fileResource1 is FileSystemResource:" + (fileResource1 instanceof FileSystemResource));

        Resource fileResource2 = resourceLoader.getResource("/Users/cindy/code/demo/build.gradle");
        System.out.println("fileResource2 is ClassPathResource:" + (fileResource2 instanceof ClassPathResource));

        Resource urlResource1 = resourceLoader.getResource("file:/Users/cindy/code/demo/build.gradle");
        System.out.println("urlResource1 is UrlResource:" + (urlResource1 instanceof UrlResource));

        Resource urlResource2 = resourceLoader.getResource("http://www.baidu.com");
        System.out.println("urlResource1 is urlResource:" + (urlResource2 instanceof  UrlResource));
    }
    
複製代碼
  • 輸出
fileResource1 is FileSystemResource:false
fileResource2 is ClassPathResource:true
urlResource1 is UrlResource:true
urlResource1 is urlResource:true
複製代碼
  • 若是將 DefaultResourceLoader 改成 FileSystemResourceLoader
fileResource1 is FileSystemResource:true
fileResource2 is ClassPathResource:false
urlResource1 is UrlResource:true
urlResource1 is urlResource:true
複製代碼
  • DefaultResourceLoader 對getResourceByPath(String location) 方法處理其實不是很恰當,FileSystemResourceLoader 覆寫了 getResourceByPath(String location) 方法,使之從文件系統加載資源並以 FileSystemResource 類型返回,這樣咱們就能夠獲得想要的資源類型
相關文章
相關標籤/搜索