Spring Resource源碼分析

轉(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è)繼承鏈。

ApplicationContext

在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)系,這里只有繼承焕议。

ClassPathXmlApplicationContext

看到相對(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ì)象)找到模塊的劃分蝙寨。
ApplicationContext

如圖所示晒衩,把整個(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接口詳解

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è)。

ResourceEditor

(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

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ò)ResourceLoaderResourcePatternResolver來(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)得及盟榴。

Resource

我們隨便找?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ò)DefaultResourceLoaderaddProtocolResolver方法來(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:的前綴厘贼。
在這里在看下ClassPathContextResourceClassPathResource的區(qū)別。ClassPathContextResourceClassPathResource的子類(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)把ResourceResourceLoader基本說(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í)慣熊咽。

ResourcePatternResolver

注釋中沒(méi)有隱含的信息,所以不做解釋闹丐。PathMatchingResourcePatternResolverResourcePatternResolver的實(shí)現(xiàn)類(lèi)。

八梨与、ApplicationContext與ResourceLoader

到了這一步文狱,先不去看 PathMatchingResourcePatternResolver的實(shí)現(xiàn)是怎么樣的瞄崇。通過(guò)類(lèi)名和實(shí)現(xiàn)的接口也能猜個(gè)七七八八。
我們把思路回到最初決策模塊拆分的那個(gè)類(lèi)圖等浊。

ApplicationContext

到目前為止凿掂,已經(jīng)理解了Resource纹蝴、ResourceLoaderResourcePatternResolver以及對(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可以看到瞳抓,具體邏輯在AbstractApplicationContextgetResources方法孩哑。

@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

AbstractApplicationContext

到這里次氨,我們畫(huà)下相關(guān)的類(lèi)圖煮寡,理解下到目前為止的源碼幸撕。

uml
@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)都在這里。

PathMatchingResourcePatternResolver

剛進(jìn)入代碼閱讀就會(huì)發(fā)現(xiàn)一個(gè)新家伙阔馋。PathMatcher接口娇掏, 并且很容易找到實(shí)現(xiàn)類(lèi)婴梧。
PathMatchingResourcePatternResolver

簡(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ò)ResourceLoaderResourcePatternResolver兩個(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)中的重要性禁灼。

參考

# Spring MVC的路徑匹配規(guī)則 Ant-style

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弄捕,一起剝皮案震驚了整個(gè)濱河市导帝,隨后出現(xiàn)的幾起案子您单,更是在濱河造成了極大的恐慌,老刑警劉巖平酿,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件染服,死亡現(xiàn)場(chǎng)離奇詭異柳刮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)秉颗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)蚕甥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)菇怀,“玉大人晌块,你說(shuō)我怎么就攤上這事匆背。” “怎么了括享?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)剩愧。 經(jīng)常有香客問(wèn)我隙咸,道長(zhǎng)成洗,這世上最難降的妖魔是什么瓶殃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任遥椿,我火速辦了婚禮淆储,結(jié)果婚禮上本砰,老公的妹妹穿的比我還像新娘。我一直安慰自己点额,他們只是感情好还棱,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布珍手。 她就那樣靜靜地躺著,像睡著了一般寡具。 火紅的嫁衣襯著肌膚如雪焙蹭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,441評(píng)論 1 310
  • 那天拯钻,我揣著相機(jī)與錄音粪般,去河邊找鬼。 笑死匙监,一個(gè)胖子當(dāng)著我的面吹牛亭姥,可吹牛的內(nèi)容都是我干的顾稀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼粮揉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扶认!你這毒婦竟也來(lái)了辐宾?” 一聲冷哼從身側(cè)響起螃概,我...
    開(kāi)封第一講書(shū)人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吊洼,失蹤者是張志新(化名)和其女友劉穎冒窍,沒(méi)想到半個(gè)月后豺鼻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埠戳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片整胃。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳钟,死狀恐怖奔则,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情共郭,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布写半,位于F島的核電站叠蝇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏铃慷。R本人自食惡果不足惜蜕该,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一堂淡、第九天 我趴在偏房一處隱蔽的房頂上張望绢淀。 院中可真熱鬧皆的,春花似錦覆履、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怪嫌。三九已至,卻和暖如春柳沙,著一層夾襖步出監(jiān)牢的瞬間岩灭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工赂鲤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留噪径,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像车摄,于是被迫代替她去往敵國(guó)和親吮播。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359