Spring IoC 學習(4)
前言
前麵的三篇文章,主要用BeanFactory介紹了Spring中IoC容器的兩個階段:容器啟動階段和實例化階段。接下來的這篇文章主要說的是Spring的統一資源定位策略。
Spring為什麼要整這個
寫下這篇文章之前的絕大部分時間,我都在思考,為什麼要整這個功能。任何一個功能、實現肯定有其道理。那道理是什麼呢?有人是這麼解釋的:
要搞清楚Spring為什麼提供這麼一個功能,還是從Java SE提供的標準類java.net.URL說起比較好。URL全名是Uniform Resource Locator(統一資源定位器),但多少有些名不副實的味道。
首先,說是統一資源定位,但基本實現卻隻限於網絡形式發布的資源的查找和定位工作,基本上隻提供了基於HTTP、FTP、File等協議(sun.net.www.protocol 包下所支持的協議)的資源定位功能。雖然也提供了擴展的接口,但從一開始,其自身的“定位”就已經趨於狹隘了。實際上,資源這個詞的範圍比較廣義,資源可以任何形式存在,如以二進製對象形式存在、以字節流形式存在、以文件形式存在等;而且,資源也可以存在於任何場所,如存在於文件係統、存在於Java應用的Classpath中,甚至存在於URL可以定位的地方。
其次,從某些程度上來說,該類的功能職責劃分不清,資源的查找和資源的表示沒有一個清晰的界限。當前情況是,資源查找後返回的形式多種多樣,沒有一個統一的抽象。理想情況下,資源查找完成後,返回給客戶端的應該是一個統一的資源抽象接口,客戶端要對資源進行什麼樣的處理,應該由資源抽象接口來界定,而不應該成為資源的定位者和查找者同時要關心的事情。
以上的這段文字來自《Spring揭秘》,寫的非常正確。但是我第一遍看的時候,我大致還是沒看懂。
可能是我底子確實太差了。也可能是因為寫書,總歸要寫的正式一點,而我對於正式語言的理解能力著實不足。這三大段文字的主要意思就是:
URL能力不足,不能定位所有類型的資源,開發Spring的這群人肯定覺得URL不好使,整了一個比它功能強的家夥
看看Spring是怎麼整的
一開始肯定是要先定義一個借口Resource
Resource
package org.springframework.core.io;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
public interface InputStreamSource{
//每次調用都將返回一個新的資源對應的java.io.InputStream字節流,調用者在使用完畢後必須關閉該資源
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource{
//返回當前Resouce代表的底層資源是否存在,true表示存在
boolean exists();
//返回當前Resouce代表的底層資源是否可讀,true表示可讀
boolean isReadable();
//返回當前Resouce代表的底層資源是否已經打開,如果返回true,則隻能被讀取一次然後關閉以避免內存泄露;常見的Resource實現一般返回false;
boolean isOpen();
//如果當前Resouce代表的底層資源能由java.util.URL代表,則返回該URL,否則拋出IOException
URL getURL() throws IOException;
//如果當前Resouce代表的底層資源能由java.util.URI代表,則返回該URI,否則拋出IOException
URI getURI() throws IOException;
//如果當前Resouce代表的底層資源能由java.io.File代表,則返回該File,否則拋出IOException
File getFile() throws IOException;
//返回當前Resouce代表的底層文件資源的長度,一般的值代表的文件資源的長度
long contentLength() throws IOException;
//返回當前Resouce代表的底層文件資源的最後修改時間
long lastModified() throws IOException;
//用於創建相對於當前Resource代表的底層資源的資源,比如當前Resource代表文件資源“D:/test/”則createRelative("test.txt")將返回代表文件資源“D:/test/test.txt”Resource資源。
Resource createRelative(String relativePath) throws IOException;
//返回當前Resource代表的底層文件資源的文件路徑,比如File資源:file://d:/test.txt 將返回d:/test.txt,而URL資源https://www.javass.cn將返回“”,因為隻返回文件路徑
String getFilename();
//返回當前Resource代表的底層資源的描述符,通常就是資源的全路徑(實際文件名或實際URL地址)
String getDescription();
}
接口提供這些方法,基本上是足夠我們日常使用的,除了這個接口,當然也提供了這些接口的sh實現類。
可以看到,有16個實現類,當然,我們常用的並沒有那麼多,主要有以下幾個
ByteArrayResource。將字節(byte)數組提供的數據作為一種資源進行封裝,如果通過InputStream形式訪問該類型的資源,該實現會根據字節數組的數據,構造相應的ByteArray-InputStream並返回。
ClassPathResource。該實現從Java應用程序的ClassPath中加載具體資源並進行封裝,可以使用指定的類加載器(ClassLoader)或者給定的類進行資源加載。
FileSystemResource。對java.io.File類型的封裝,所以,我們可以以文件或者URL的形式對該類型資源進行訪問,隻要能跟File打的交道,基本上跟FileSystemResource也可以。
UrlResource。通過java.net.URL進行的具體資源查找定位的實現類,內部委派URL進行具
體的資源操作。InputStreamResource。將給定的InputStream視為一種資源的Resource實現類,較為少用。
可能的情況下,以ByteArrayResource以及其他形式資源實現代之。
ResourceLoader
其實啦,上麵說了半天,都是在說Resource,有了資源,怎麼去查找,怎麼去定位呢,用ResourceLoader。
這必然,我猜它就是個接口,這麼簡潔的名字。
ResourceLoader
package org.springframework.core.io;
import org.springframework.util.ResourceUtils;
public interface ResourceLoader {
// CLASSPATH_URL_PREFIX = classpath
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 拿到Resource
Resource getResource(String location);
// 拿到ClassLoader
ClassLoader getClassLoader();
}
事實證明,我想的還是沒錯的,但是這個接口也真是夠簡潔的。
看看實現類吧~
18個,但還是先看畫圈的幾個吧。
DefaultResourceLoader
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
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);
}
}
}
這是DefaultResourceLoader返回Resource的方法,可以看到:
看看location是不是以"/"開頭,如果是返回ClassPathContextResource類型的資源
檢查資源路徑是否以classpath:前綴打頭,如果是,則嚐試構造ClassPathResource類型資源並返回。
嚐試通過URL,根據資源路徑來定位資源,如果沒有拋出MalformedURLException,有則會構造UrlResource類型的資源並返回;
4.如果還是無法根據資源路徑定位指定的資源,則委派getResourceByPath(String) 方法來定位, DefaultResourceLoader 的getResourceByPath(String)方法默認實現邏輯是,構造ClassPathResource類型的資源並返回。
FileSystemResourceLoader雖然是DefaultResourceLoader的繼承類,但是在getResourceByPaht方法上就不是返回ClassPathResource,而是返回FileSystemResource
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemContextResource(path);
}
ApplicationContext 與資源
前麵一直就隻是在講資源,資源定位器,但是,在我們Spring的體現又在哪裏?
先看這個圖,很明顯,ApplicationContext也是ResourceLoader。如果再看兩個重要的實現類,你就會懂了。原來如此
ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
剩下的,你說一個bean裏麵,如果要注入ResourceLoader,或者要注入Resource怎麼辦呢?
可以這麼做
public class FooBar {
private ResourceLoader resourceLoader;
public void foo(String location) {
System.out.println(getResourceLoader().getResource(location).getClass());
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
然後配置一下bens.xml
<bean >
</bean>
<bean >
<property name="resourceLoader">
<ref bean="resourceLoader"/> </property>
</bean>
有點複雜誒,你說,ApplicationContext不就是資源定位器嗎?為什麼還要再弄一個新的出來呢?
前一小節的兩個接口有用了,ResourceLoaderAware和ApplicationContextAware,他們都能拿到IoC容器本身呀。
public class FooBar implements ApplicationContextAware {
private ResourceLoader resourceLoader;
public void foo(String location) {
System.out.println(getResourceLoader().getResource(location).getClass());
}
public ResourceLoader getResourceLoader() {
return resourceLoader;
}
public void setApplicationContext(ApplicationContext ctx)
throws BeansException
{
this.resourceLoader = ctx;
}
}
看這配置文件不就隻有一行了,方便。
<bean >
</bean>
後記
寫完這篇,基本算是把資源一部分搞懂了一點,但是最近一直在反思,這種總結的辦法,可以我是確實不會用Markdown吧,每次花在編輯上的時間就非常多了。還是要想想辦法解決這個問題。
最後更新:2017-09-02 16:02:42