轉(zhuǎn)載請(qǐng)注明出處即可媒怯。
上篇文章描述了閱讀Spring源碼的相關(guān)思考閱讀Spring Frameworks源碼的思考订讼,在這里就按照文章中所描述的思維方式來(lái)進(jìn)行分析和拆解髓窜,并進(jìn)行一些核心類(lèi)的解析扇苞。
一、構(gòu)建Demo項(xiàng)目
我們先來(lái)構(gòu)建一個(gè)簡(jiǎn)單的項(xiàng)目,
pom.xml引入了Spring boot寄纵,里面的Spring是5.1.9的版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>study</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
然后在項(xiàng)目的resources目錄下編寫(xiě)一個(gè)bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myTestBean" class="org.study.MyTestBean"/>
</beans>
在編寫(xiě)一個(gè)Bean
public class MyTestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
最后補(bǔ)充一個(gè)main
public class Run {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
MyTestBean bean = (MyTestBean) context.getBean("myTestBean");
System.out.println(bean.getTestStr());
}
}
這應(yīng)該是使用Spring的最簡(jiǎn)單的方式之一鳖敷。如果到這里項(xiàng)目可以執(zhí)行了,那么就已經(jīng)可以進(jìn)入源碼閱讀的歷程了程拭,并不需要去clone框架的源碼定踱。
二、模塊拆解
先看下Run類(lèi)中的3行代碼恃鞋,目標(biāo)是搞清楚崖媚,這三行的底層是怎么實(shí)現(xiàn)的。那么在理清之前恤浪,優(yōu)先應(yīng)該關(guān)注的就是"接口"畅哑。因?yàn)榻涌谧鳛榭蚣?對(duì)外"提供的"功能描述",很容易通過(guò)接口所提供的方法來(lái)知曉可以利用的框架功能水由。并且通過(guò)接口還可以在接口的繼承鏈中荠呐,或者該接口實(shí)現(xiàn)類(lèi)的屬性中(對(duì)象組合)找到"模塊"的劃分,進(jìn)而根據(jù)不同的模塊閱讀來(lái)分別理解Spring的某一項(xiàng)功能的實(shí)現(xiàn)原理砂客,整理出模塊內(nèi)部類(lèi)與類(lèi)之間的關(guān)系泥张,最后在整合出模塊與模塊之間的關(guān)系,并了解他們是如何配合ApplicationContext
來(lái)提供相關(guān)功能的鞠值。
ApplicationContext
是這3行代碼中唯一的接口媚创。我們看下這個(gè)繼承鏈。
在Java中一般只有兩種方式來(lái)擴(kuò)展自身接口或者類(lèi)的功能彤恶,一種是繼承另一種是組合筝野,ApplicationContext
的繼承關(guān)系只表明了對(duì)外提供了哪些功能晌姚,并不代表著這些功能一定是ApplicationContext
的實(shí)現(xiàn)類(lèi)來(lái)實(shí)現(xiàn)的,可能會(huì)委托給其他的類(lèi)歇竟。所以在貼一張ClassPathXmlApplicationContext
的繼承鏈挥唠,當(dāng)然還缺失了引用的關(guān)系,這里只有繼承焕议。
看到相對(duì)較繁瑣的繼承關(guān)系可能已經(jīng)抓不住頭腦了宝磨。不過(guò)沒(méi)關(guān)系,我們對(duì)源碼的拆解是一步一步的不會(huì)步子跨的太大盅安,可以先對(duì)這張圖有個(gè)基本的概念唤锉,后面我們會(huì)發(fā)現(xiàn),對(duì)于找到對(duì)應(yīng)的實(shí)現(xiàn)别瞭,這張圖還是很有作用的窿祥。前面剛說(shuō)可以在接口的繼承鏈或者,實(shí)現(xiàn)類(lèi)的屬性中(對(duì)象)找到模塊的劃分蝙寨。
如圖所示晒衩,把整個(gè)
ApplicationContext
的接口劃分為5個(gè)模塊,并且認(rèn)為ApplicationContext
至少是由這5個(gè)模塊構(gòu)成(這里其實(shí)沒(méi)考慮對(duì)象組合的那些類(lèi))墙歪。我們?cè)谶@篇先看下
ResourceLoader
接口听系,其他的接口在后續(xù)拆解IOC和事件發(fā)布的時(shí),在詳細(xì)看虹菲。先找一個(gè)切入點(diǎn)進(jìn)行入手靠胜。
三、ResourceLoader接口詳解
通過(guò)接口的名稱(chēng)資源加載器毕源,我們就能知曉接口的功能浪漠,無(wú)非就是加載資源而已。那么問(wèn)題來(lái)了霎褐,一個(gè)是什么是資源? 另一個(gè)是如何加載資源址愿。要解答這兩個(gè)問(wèn)題,接口上的注釋和接口的方法其實(shí)就是答案了瘩欺。
注釋上首先提供了3個(gè)最重要的信息
(1) 加載的資源可能在classpath下必盖,也能可能只是文件系統(tǒng)中的一個(gè)資源。那么這里其實(shí)就可以知曉俱饿,所謂的資源就是某一個(gè)文件歌粥,只是隨著加載策略的不同,讀取的文件的路徑也不同拍埠。
(2) 這個(gè)接口的功能是ApplicationContext
必須提供的失驶,至于為什么,后面的注釋有解釋枣购。
(3) 通過(guò)繼承于ResourceLoader
的接口ResourcePatternResolver
提供了更多的功能嬉探。
在這里也可以看到單一職責(zé)原則的好處了吧擦耀。注釋上涵蓋的幾條信息就已經(jīng)描述清楚了接口的職責(zé)。
注釋還沒(méi)解釋完涩堤,但是這里還是要在對(duì)上面3個(gè)信息多說(shuō)一些眷蜓。
所謂資源就是某一個(gè)文件,對(duì)于這個(gè)描述胎围,如果看過(guò)源碼的人可能就會(huì)噴我了吁系。但是如果沒(méi)有完整的把所有細(xì)節(jié)都看完,只是看注釋來(lái)得出這個(gè)結(jié)論也無(wú)傷大雅白魂。隨著閱讀量的增加必然的會(huì)修正早期閱讀理解的錯(cuò)誤汽纤。
其他兩段注釋描述了兩個(gè)信息
(1) DefaultResourceLoader
可以獨(dú)立于ApplicationContext
來(lái)使用,這個(gè)實(shí)現(xiàn)類(lèi)也在被ResourceEditor
類(lèi)使用福荸,我們點(diǎn)進(jìn)去看下這個(gè)類(lèi)蕴坪,發(fā)現(xiàn)構(gòu)造器上確實(shí)new 了一個(gè)。
(2) 在
ApplicationContext
運(yùn)行時(shí)敬锐,如果某個(gè)Bean的屬性存在Resource或者Resource[]背传,可以通過(guò)字符串來(lái)使用加載策略。至于只用的什么加載策略滞造,我們后面再看续室。
在看下ResourceLoader
的接口中的方法和屬性
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
@Nullable
ClassLoader getClassLoader();
}
先看下CLASSPATH_URL_PREFIX
屬性栋烤。順便看下ResourceUtils
其實(shí)到這里谒养,就可以大概猜出來(lái)
ResourceLoader
的策略模式使如何實(shí)現(xiàn)的了。
在調(diào)用getResource(String location)
方法時(shí)明郭,通過(guò)location的不同的前綴來(lái)執(zhí)行不同的Resource
獲取策略买窟。在查看具體實(shí)現(xiàn)之前,先了解下Resource接口薯定,畢竟已經(jīng)出現(xiàn)在ResourceLoader接口里面了始绍。
四、Resource接口詳解
對(duì)于Resource接口话侄,其實(shí)不用多說(shuō)亏推,畢竟注釋已經(jīng)寫(xiě)得非常清楚了。
/**
* Interface for a resource descriptor that abstracts from the actual
* type of underlying resource, such as a file or class path resource.
*
* <p>An InputStream can be opened for every resource if it exists in
* physical form, but a URL or File handle can just be returned for
* certain resources. The actual behavior is implementation-specific.
*
*/
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return exists();
}
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();
}
通過(guò)注釋總結(jié)下來(lái)的有幾點(diǎn)年堆。
(1) Resource
在Spring中是作為所有資源的抽象吞杭。如果要獲取某個(gè)資源,需要通過(guò)ResourceLoader
或ResourcePatternResolver
來(lái)進(jìn)行獲取(后面再說(shuō)兩個(gè)接口的區(qū)別是什么)变丧。
(2) 資源不一定是一個(gè)文件芽狗,可以是個(gè)網(wǎng)頁(yè),也可以只是遠(yuǎn)程對(duì)象存儲(chǔ)的一個(gè)對(duì)象(Spring的實(shí)現(xiàn)里面沒(méi)有痒蓬,但不妨礙我們自己封裝一個(gè)Resource的子類(lèi))童擎。
(3) 大部分資源都可以獲取資源的InputStream
, 當(dāng)然Resource
也繼承了InputStreamSource
滴劲,除非有其他的一些特定的實(shí)現(xiàn)以外。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
我們已經(jīng)解釋了2個(gè)接口顾复,并且提到了一個(gè)ResourceLoader
的實(shí)現(xiàn)類(lèi)班挖。為了加深印象,我們來(lái)使用下相關(guān)的實(shí)現(xiàn)類(lèi)和接口芯砸。
五聪姿、Resource和ResourceLoader的使用
先看下Resource
的實(shí)現(xiàn)類(lèi)。通過(guò)類(lèi)名我們其實(shí)可以很清楚的了解大部分實(shí)現(xiàn)類(lèi)的功能乙嘀。部分理解不了的末购,可以先放放,后面的源碼閱讀遇到時(shí)虎谢,現(xiàn)讀也來(lái)得及盟榴。
我們隨便找?guī)讉€(gè)實(shí)現(xiàn)類(lèi)試下,具體實(shí)現(xiàn)類(lèi)不用解釋?zhuān)?lèi)型表述的很清楚了婴噩。
Resource fileSystemResource = new FileSystemResource("/Users/wangzedong/Documents/java-project/study/src/main/resources/bean.xml");
assert fileSystemResource.exists();
Resource classPathResource = new ClassPathResource("bean.xml");
assert classPathResource.exists();
Resource urlResource = new UrlResource("https://www.baidu.com");
assert urlResource.exists();
其實(shí)在使用Spring框架的過(guò)程中擎场,如果也有讀取資源的情況,不妨用Resource
來(lái)獲取几莽。但是如果資源的種類(lèi)比較多的話(huà)可能就需要加各種判斷來(lái)區(qū)分使用哪個(gè)子類(lèi)迅办。說(shuō)到這,你是不是又想起來(lái)ResourceLoader
和它的實(shí)現(xiàn)類(lèi)DefaultResourceLoader
了? 沒(méi)錯(cuò)章蚣,這貨就是在干這個(gè)工作站欺。
我們來(lái)寫(xiě)段代碼試下
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("/Users/wangzedong/Documents/java-project/study/src/main/resources/bean.xml");
assert resource instanceof FileSystemResource;
assert resource.exists();
resource = resourceLoader.getResource("bean.xml");
assert resource instanceof ClassPathResource;
assert resource.exists();
resource = resourceLoader.getResource("https://www.baidu.com");
assert resource instanceof UrlResource;
assert resource.exists();
前兩個(gè)斷言是執(zhí)行不過(guò)去的。后四個(gè)是沒(méi)有問(wèn)題的纤垂,這和DefaultResourceLoader
的實(shí)現(xiàn)邏輯有關(guān)矾策。到這里我們?cè)谠敿?xì)看下DefaultResourceLoader
的實(shí)現(xiàn)
六、DefaultResourceLoader的實(shí)現(xiàn)
下面是getResource
方法的源碼峭沦,我們也分為幾個(gè)塊來(lái)閱讀
@Override
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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
(1) 第一步中贾虽,先循環(huán)協(xié)議解析器,默認(rèn)情況下協(xié)議解析器的集合雖然由容量但是里面并沒(méi)有相關(guān)對(duì)象
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
所以可以通過(guò)DefaultResourceLoader
的addProtocolResolver
方法來(lái)添加
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
其實(shí)DefaultResourceLoader
僅僅實(shí)現(xiàn)了幾個(gè)策略(協(xié)議)蓬豁,甚至連ResourceUtils
常量里面的策略(協(xié)議)都沒(méi)有完全實(shí)現(xiàn)菇肃。為了提高代碼的靈活性,這里巷送,使用了集合來(lái)處理用戶(hù)自定義的協(xié)議解析規(guī)則。我們只需要實(shí)現(xiàn)ProtocolResolver
接口,并通過(guò)addProtocolResolver
添加到protocolResolvers
集合中就好聊品。因?yàn)楸容^簡(jiǎn)單就不寫(xiě)具體的示例了,感興趣的可以自己實(shí)現(xiàn)下試試翻屈。
@FunctionalInterface
public interface ProtocolResolver {
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);
}
(2) 第二步判斷如果是"/"開(kāi)頭,則使用Resource
的實(shí)現(xiàn)類(lèi)ClassPathContextResource
妻坝。這也解釋了前面兩個(gè)斷言為什么執(zhí)行無(wú)法通過(guò)伸眶。并不是通過(guò)FileSystemResource
來(lái)實(shí)現(xiàn)的。
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
如果是classpath:
開(kāi)頭的字符串會(huì)通過(guò)ClassPathResource
來(lái)實(shí)現(xiàn)刽宪,創(chuàng)建之前還去掉了classpath:
的前綴厘贼。
在這里在看下ClassPathContextResource
和ClassPathResource
的區(qū)別。ClassPathContextResource
是ClassPathResource
的子類(lèi)圣拄。在ClassPatchContextResource
的構(gòu)造器中嘴秸,必須要傳一個(gè)ClassLoader
,但如果DefaultResourceLoader
里面不指定的話(huà)庇谆,用的都是ClassUtils.getDefaultClassLoader()
岳掐。
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
(3) 第三步比較簡(jiǎn)單了,判斷下是不是url,然后在判斷url指向的是文件叫惊,還是遠(yuǎn)程的地址。并創(chuàng)建相應(yīng)的對(duì)象纲酗。url解析失敗了還會(huì)嘗試用ClassPathContextResource
來(lái)試試。
到此為止逝淹,我們已經(jīng)把Resource
和ResourceLoader
基本說(shuō)清楚了耕姊,但是別忘了桶唐,還有一個(gè)ResourcePatternResolver
接口需要研究栅葡。
七、ResourcePatternResolver詳解
其實(shí)不看注釋?zhuān)豢捶椒ㄓ仍螅材苤朗亲隽俗x取多個(gè)Resource的擴(kuò)展欣簇。并且locationPattern是可以傳通配符的。
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
但為了理解源碼作者的意圖坯约,略過(guò)注釋可不是一個(gè)好習(xí)慣熊咽。
注釋中沒(méi)有隱含的信息,所以不做解釋闹丐。
PathMatchingResourcePatternResolver
是ResourcePatternResolver
的實(shí)現(xiàn)類(lèi)。
八梨与、ApplicationContext與ResourceLoader
到了這一步文狱,先不去看 PathMatchingResourcePatternResolver
的實(shí)現(xiàn)是怎么樣的瞄崇。通過(guò)類(lèi)名和實(shí)現(xiàn)的接口也能猜個(gè)七七八八。
我們把思路回到最初決策模塊拆分的那個(gè)類(lèi)圖等浊。
到目前為止凿掂,已經(jīng)理解了
Resource
纹蝴、ResourceLoader
和ResourcePatternResolver
以及對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)的原理(還剩一個(gè)實(shí)現(xiàn)類(lèi)沒(méi)看)。那么從具體的樹(shù)木觀察走出來(lái)糠涛,我們看看這片小森林忍捡。需要關(guān)注下這里個(gè)接口之間以及實(shí)現(xiàn)類(lèi)之間有什么關(guān)系呢?我們先寫(xiě)一個(gè)demo
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Resource resource = context.getResource("bean.xml");
Resource[] resources = context.getResources("bean.xml");
因?yàn)?code>ApplicationContext的繼承砸脊,所以它毫無(wú)意外的擁有這兩個(gè)方法纬霞。我們?cè)倏聪?code>getResources方法實(shí)現(xiàn)在了哪里。
通過(guò)idea可以看到瞳抓,具體邏輯在AbstractApplicationContext
的getResources
方法孩哑。
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
在繼續(xù)在AbstractApplicationContext
跟下this.resourcePatternResolver
的創(chuàng)建
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
根據(jù)構(gòu)造器和方法横蜒,發(fā)現(xiàn)AbstractApplicationContext
并沒(méi)有親力親為,而是將相關(guān)邏輯委托給了PathMatchingResourcePatternResolver
鹰霍。
再看下PathMatchingResourcePatternResolver
的構(gòu)造器, 上面AbstractApplicationContext
把this傳入了構(gòu)造器茵乱。
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
看到這里說(shuō)明AbstractApplicationContext
需要繼承ResourceLoader
的子類(lèi)瓶竭,或者自己實(shí)現(xiàn)getResource
方法,但是畢竟已經(jīng)存在了DefaultResourceLoader
智哀,無(wú)需在復(fù)寫(xiě)一遍代碼瓷叫。所以我們可以看到AbstractApplicationContext
為了實(shí)現(xiàn)ResourceLoader
送巡,繼承了DefaultResourceLoader
。
到這里次氨,我們畫(huà)下相關(guān)的類(lèi)圖煮寡,理解下到目前為止的源碼幸撕。
@startuml
interface ResourceLoader
interface ResourcePatternResolver
interface ApplicationContext
interface ConfigurableApplicationContext
class DefaultResourceLoader
abstract class AbstractApplicationContext {
private ResourcePatternResolver resourcePatternResolver;
}
class PathMatchingResourcePatternResolver
ResourceLoader <|.. DefaultResourceLoader: 實(shí)現(xiàn)
DefaultResourceLoader <|-- AbstractApplicationContext: 繼承
ResourceLoader <|-- ResourcePatternResolver: 繼承
ResourcePatternResolver <|-- ApplicationContext: 繼承
ApplicationContext <|-- ConfigurableApplicationContext: 繼承
ConfigurableApplicationContext <|.. AbstractApplicationContext: 實(shí)現(xiàn)
ResourcePatternResolver <|.. PathMatchingResourcePatternResolver: 實(shí)現(xiàn)
PathMatchingResourcePatternResolver .. AbstractApplicationContext: 引用
@enduml
九坐儿、PathMatchingResourcePatternResolver詳解
具體的關(guān)系理清后挑童,我們最后再看下具體的實(shí)現(xiàn)類(lèi)跃须。
直接看getResources
方法菇民,因?yàn)殛P(guān)鍵實(shí)現(xiàn)都在這里。
剛進(jìn)入代碼閱讀就會(huì)發(fā)現(xiàn)一個(gè)新家伙阔馋。
PathMatcher
接口娇掏, 并且很容易找到實(shí)現(xiàn)類(lèi)婴梧。簡(jiǎn)單來(lái)看其實(shí)就是一個(gè)路徑匹配器,因?yàn)榉椒êx很清楚孽江,并且注釋也寫(xiě)的很詳細(xì)岗屏。在這就簡(jiǎn)單描述下每個(gè)方法的作用漱办。
public interface PathMatcher {
// 驗(yàn)證路徑是否是一個(gè)需要匹配的字符串,比如 /**/*.xml娩井。如果不是,只是一個(gè)靜態(tài)路徑只需要直接讀取即可率碾,無(wú)需在判斷match
boolean isPattern(String path);
// 驗(yàn)證path 和模式(patten)字符串是否匹配
boolean match(String pattern, String path);
// 這里代表著前綴匹配屋彪,和match方法區(qū)別是,如果只是字符串后面匹配了仔粥,但是前綴不匹配依然會(huì)返回false
boolean matchStart(String pattern, String path);
// 這個(gè)方法是提取出匹配的部分字符串
String extractPathWithinPattern(String pattern, String path);
// 這個(gè)方法是用于提取uri變量的, 直接用注釋里的例子 : pattern 為 "/hotels/{hotel}" 蟹但,
// 路徑為 "/hotels/1", 則該方法會(huì)返回一個(gè) map為 : "hotel"->"1".
Map<String, String> extractUriTemplateVariables(String pattern, String path);
// 通過(guò)path返回一個(gè)Comparator, 可以用于排序
Comparator<String> getPatternComparator(String path);
// 合并兩個(gè)模式
String combine(String pattern1, String pattern2);
}
然后我們看下子類(lèi)AntPathMatcher
华糖,發(fā)現(xiàn)Spring使用的是Apache Ant的樣式路徑(https://ant.apache.org),具體的實(shí)現(xiàn)我覺(jué)得無(wú)需多言诵竭,無(wú)非就是字符串的操作,感興趣的可以看看具體的實(shí)現(xiàn)沙郭。
通配符 | 描述 |
---|---|
? | 匹配任何單字符 |
* | 匹配0或者任意數(shù)量的字符 |
** | 匹配0或者更多的目錄 |
路徑 | 描述 |
---|---|
/app/*.x |
匹配(Matches)所有在app路徑下的.x文件 |
/app/p?ttern |
匹配(Matches) /app/pattern 和 /app/pXttern,但是不包括/app/pttern |
/**/example |
匹配(Matches) /app/example, /app/foo/example, 和 /example |
/app/**/dir/file |
匹配(Matches) /app/dir/file.jsp, /app/foo/dir/file.html,/app/foo/bar/dir/file.pdf, 和 /app/dir/file.java |
/**/*.jsp |
匹配(Matches)任何的.jsp 文件 |
了解到這里病线,我們?cè)诨仡^看下getResources
方法鲤嫡。
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 判斷是否是classpath*:開(kāi)頭
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
// 判斷是否有?和*,以及{}让虐,根據(jù)前面的描述,如果存在模式赡突,則需要對(duì)根目錄下的所有資源的path進(jìn)行match
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
/**
* 執(zhí)行到這說(shuō)明可能是靜態(tài)字符串惭缰,直接找具體的資源就好漱受,或者可能是classpath*:骡送,要找jar的目錄
* 當(dāng)然方法名稱(chēng)中還隱含了其他信息,因?yàn)槿绻麅H僅是在當(dāng)前項(xiàng)目下的查找方法名稱(chēng)直接叫做findClassPathResources就好了
* 我們可以看下doFindAllClassPathResources的實(shí)現(xiàn)虐先,其實(shí)也包含jar包蛹批。
*/
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// 以下邏輯和上面類(lèi)似腐芍,不在多說(shuō)
// 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
// 這里的ResourceLoader猪勇,其實(shí)就是AbstractApplicationContext
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
然后我們?cè)诳聪?code>findPathMatchingResources
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
// 獲取根路徑, 也就是classpath*:
String rootDirPath = determineRootDir(locationPattern);
// 獲取子路徑, 也就是*.xml
String subPattern = locationPattern.substring(rootDirPath.length());
// 通過(guò)根路徑在此調(diào)用getResources, 其實(shí)也就是邏輯了jar里面的根路徑, 因?yàn)閕sPattern執(zhí)行是false了
Resource[] rootDirResources = getResources(rootDirPath);
// 結(jié)果集合
Set<Resource> result = new LinkedHashSet<>(16);
// 遍歷所有的路徑
for (Resource rootDirResource : rootDirResources) {
// 這步什么都沒(méi)錯(cuò)埠对,只是一個(gè)return, 但是方法是protected的裁替,也就是說(shuō)這里是個(gè)模板方法的模式
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
// 判讀是否是vfs前綴
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// 判讀是否是jar
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
// 如果都不是可能只是一個(gè)普通文件
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
看到這里我們基本已經(jīng)把核心邏輯捋清了弱判,當(dāng)然還有部分實(shí)現(xiàn)邏輯沒(méi)有往下繼續(xù)寫(xiě),但肯定少不了getPathMatcher().match的調(diào)用开伏。比較簡(jiǎn)單的邏輯就不在這里完全把細(xì)節(jié)寫(xiě)完了固灵。
十劫流、總結(jié)
到這里基本已經(jīng)就把Resource
相關(guān)的內(nèi)容看完了祠汇,當(dāng)然可能還有疑問(wèn),為什么Spring在ApplicationContext
中實(shí)現(xiàn)相關(guān)功能诗力。其實(shí)無(wú)論是properties苇本、yaml和xml都是可以作為Resource
來(lái)進(jìn)行描述的菜拓。在Spring中需要大量的獲取資源的的操作。如果每個(gè)使用資源的地方都去寫(xiě)一遍類(lèi)似的邏輯顯然是不符合面向?qū)ο蟮脑O(shè)計(jì)原則的康栈。所以Spring中通過(guò)Resource
來(lái)抽象了對(duì)資源的獲取啥么。并通過(guò)ResourceLoader
和ResourcePatternResolver
兩個(gè)接口抽象了資源的加載策略贰逾,進(jìn)而為其他的類(lèi)提供服務(wù)。我們也可以看到Resource
相關(guān)的接口和實(shí)現(xiàn)類(lèi)都是在Spring的core包中的氯迂,具體為org.springframework.core.io
,在這點(diǎn)上也可以看到相關(guān)的接口和類(lèi)在Spring功能實(shí)現(xiàn)中的重要性禁灼。