1. 程式人生 > >spring core io包原始碼解析

spring core io包原始碼解析

一、

一、Resource介面和WritableResource介面
    InputStreamSource介面是Resource介面的父介面,Resource介面表示一個可讀的資源物件的公共方法,包含的方法如下圖所示。其中readableChannel方法返回的是NIO裡面的ReadableByteChannel物件,其中createRelative(String path)方法表示建立一個基於當前路徑的相對路徑的資源物件,參考具體實現的測試用例。

AbstractResource是Resource介面的抽象實現類,提供了部分方法的預設實現,注意該類覆寫了equals(Object other)和hashCode()方法,依據getDescription()返回值判斷。
DescriptiveResource是一個Resource介面的空實現
ByteArrayResource表示位元組陣列資源
VfsResource表示Jboss VFS檔案的資源,通過VfsUtils實現,由於Jboss VFS相容性較差,在非jboss應用不推薦使用InputStreamResource 表示輸入流的的資源
AbstractFileResolvingResource 提供了網路或者本地檔案資源的抽象實現類,對網路檔案資源通過設定請求方法為HEAD來獲取檔案是否可以訪問,大小和最後一次修改時間
UrlResource繼承自AbstractFileResolvingResource,重點重寫了equals(Object other)方法和hashCode()方法,通過比較每個原始URL字串對應的cleanUrl判斷,該方法可以將windowns中的\\轉換成/,將../ 和 ./ 翻譯成實際的目錄。參考如下測試用例:
@Test
	public void testUrlResource3() throws IOException {
		System.out.println(StringUtils.cleanPath("file:org/springframework/core/../core/io/./Resource.class"));
		System.out.println(StringUtils.cleanPath("file:org/springframework/core/../asm/./Edge.class"));
		System.out.println(StringUtils.cleanPath("file:org/springframework/core/../../../asm/./Edge.class"));
		System.out.println(StringUtils.cleanPath("file:\\dir\\test.txt?argh"));
	}

該用例的結果如下:

file:org/springframework/core/io/Resource.class
file:org/springframework/asm/Edge.class
file:asm/Edge.class
file:/dir/test.txt?argh

該方法的原始碼說明:

public static String cleanPath(String path) {
		if (!hasLength(path)) {
			return path;
		}
		//替換windows中的\\
		String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR);


		int prefixIndex = pathToUse.indexOf(':');
		String prefix = "";
		if (prefixIndex != -1) {
			prefix = pathToUse.substring(0, prefixIndex + 1);
			if (prefix.contains(FOLDER_SEPARATOR)) {
				prefix = "";
			}
			else {
				pathToUse = pathToUse.substring(prefixIndex + 1);
			}
		}
		if (pathToUse.startsWith(FOLDER_SEPARATOR)) {
			prefix = prefix + FOLDER_SEPARATOR;
			pathToUse = pathToUse.substring(1);
		}

		// 按路徑分隔符/拆分成字串組數
		String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR);
		// 採用LinkedList是因為該List實現支援更靈活的插入和刪除某個index的元素的操作
		LinkedList<String> pathElements = new LinkedList<>();


		int tops = 0;
		//此處從陣列中最後一個元素開始遍歷
		for (int i = pathArray.length - 1; i >= 0; i--) {
			String element = pathArray[i];
			//當前路徑元素./
			if (CURRENT_PATH.equals(element)) {
				// Points to current directory - drop it.
			}
			//上一級路徑元素 ../
			else if (TOP_PATH.equals(element)) {
				// Registering top path found.
				tops++;
			}
			else {
				if (tops > 0) {
					// Merging path element with element corresponding to top path.
					//上一個元素是上一級路徑,則該元素被合併了
					tops--;
				}
				else {
					// Normal path element found.
					//每次新增都是新增到index為0的位置
					pathElements.add(0, element);
				}
			}
		}
//		 Remaining top paths need to be retained.
		for (int i = 0; i < tops; i++) {
			pathElements.add(0, TOP_PATH);
		}

		//個人提供的另一種更簡潔的實現,正向遍歷
//		for (int i = 0; i <pathArray.length;i++) {
//			String element = pathArray[i];
		    //如果是上一級元素且元素陣列中最後一個元素不是上一級元素,則移除該元素
//			if (TOP_PATH.equals(element) && !pathElements.getLast().equals(TOP_PATH)) {
//				pathElements.removeLast();
//			}else if(!element.equals(CURRENT_PATH)){
//				pathElements.addLast(element);
//			}
//		}

		// If nothing else left, at least explicitly point to current path.
		if (pathElements.size() == 1 && "".equals(pathElements.getLast()) && !prefix.endsWith(FOLDER_SEPARATOR)) {
			pathElements.add(0, CURRENT_PATH);
		}
        //將字首和路徑元素合併
		return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR);
	}
ClassPathResource類繼承自AbstractFileResolvingResource抽象類,提供了基於Class物件和ClassLoader物件獲取資源的方法,都不為空的情況下優先使用Class物件獲取資源,兩者都為空則使用SystemClassLoader獲取資源,需要注意基於Class獲取資源時如果沒有帶/是基於該Class所在的類路徑,如果帶/是基於應用的根類路徑,基於ClassLoader獲取資源不能帶/,只能基於應用的根類路徑,參考如下測試用例,test.txt檔案在ResourceTests.java同目錄下有一個,在resources資料夾下也有一個。
@Test
	public void testClassPath() throws IOException {
		System.out.println(ResourceTests.class.getResource("test.txt"));
		System.out.println(ResourceTests.class.getResource("/test.txt"));
		System.out.println(ResourceTests.class.getClassLoader().getResource("test.txt"));
		System.out.println(ResourceTests.class.getClassLoader().getResource("/test.txt"));
	}

該用例的輸出如下:

file:/D:/git/spring-framework/spring-core/out/test/classes/org/springframework/core/io/test.txt
file:/D:/git/spring-framework/spring-core/out/test/resources/test.txt
file:/D:/git/spring-framework/spring-core/out/test/resources/test.txt
null

二、WritableResource介面

WritableResource介面繼承自Resource介面,表示一個可寫的資源。

PathResource繼承自AbstractResource類並實現了WritableResource介面,是將檔案路徑或者URI轉化成NIO的Path物件,藉助NIO相關方法實現介面的。
FileSystemResource繼承自AbstractResource類並實現了WritableResource介面,藉助Path物件和File物件實現介面。
FileUrlResource繼承自UrlResource並實現了WritableResource介面,藉助Resource介面getFile()方法,通過File物件實現介面。