<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    在《深入Spring IOC源碼之Resource》中已經詳細介紹了SpringResource的抽象,Resource接口有很多實現類,我們當然可以使用各自的構造函數創建符合需求的Resource實例,然而Spring提供了ResourceLoader接口用于實現不同的Resource加載策略,即將不同Resource實例的創建交給ResourceLoader來計算。

    public interface ResourceLoader {

        //classpath

        String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

        Resource getResource(String location);

        ClassLoader getClassLoader();

    }

    ResourceLoader接口中,主要定義了一個方法:getResource(),它通過提供的資源location參數獲取Resource實例,該實例可以是ClasPathResourceFileSystemResourceUrlResource等,但是該方法返回的Resource實例并不保證該Resource一定是存在的,需要調用exists方法判斷。該方法需要支持一下模式的資源加載:

    1.       URL位置資源,如”file:C:/test.dat”

    2.       ClassPath位置資源,如”classpath:test.dat”

    3.       相對路徑資源,如”WEB-INF/test.dat”,此時返回的Resource實例根據實現不同而不同。

    ResourceLoader接口還提供了getClassLoader()方法,在加載classpath下的資源時作為參數傳入ClassPathResource。將ClassLoader暴露出來,對于想要獲取ResourceLoader使用的ClassLoader用戶來說,可以直接調用getClassLoader()方法獲得,而不是依賴于Thread Context ClassLoader,因為有些時候ResourceLoader內部使用自定義的ClassLoader

    在實際開發中經常會遇到需要通過某種匹配方式查找資源,而且可能有多個資源匹配這種模式,在Spring中提供了ResourcePatternResolver接口用于實現這種需求,該接口繼承自ResourceLoader接口,定義了自己的模式匹配接口:

    public interface ResourcePatternResolver extends ResourceLoader {

        String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

        Resource[] getResources(String locationPattern) throws IOException;

    }

    ResourcePatternResolver定義了getResources()方法用于根據傳入的locationPattern查找和其匹配的Resource實例,并以數組的形式返回,在返回的數組中不可以存在相同的Resource實例。ResourcePatternResolver中還定義了”classpath*:”模式,用于表示查找classpath下所有的匹配Resource

    Spring中,對ResourceLoader提供了DefaultResourceLoaderFileSystemResourceLoaderServletContextResourceLoader等單獨實現,對ResourcePatternResolver接口則提供了PathMatchingResourcePatternResolver實現。并且ApplicationContext接口繼承了ResourcePatternResolver,在實現中,ApplicationContext的實現類會將邏輯代理給相關的單獨實現類,如PathMatchingResourceLoader等。在ApplicationContextResourceLoaderAware接口,可以將ResourceLoader(自身)注入到實現該接口的Bean中,在Bean中可以將其強制轉換成ResourcePatternResolver接口使用(為了安全,強轉前需要判斷)。在Spring中對ResourceLoader相關類的類圖如下:


    DefaultResourceLoader

    DefaultResourceLoaderResourceLoader的默認實現,AbstractApplicationContext繼承該類(關于這個繼承,簡單吐槽一下,Spring內部感覺有很多這種個人感覺使用組合更合適的繼承,比如還有AbstractBeanFactory繼承自FactoryBeanRegisterySupport,這個讓我看起來有點不習慣,而且也增加了類的繼承關系)。它接收ClassLoader作為構造函數的參數,或使用不帶參數的構造函數,此時ClassLoader使用默認的ClassLoader(一般為Thread Context ClassLoader),ClassLoader也可以通過set方法后繼設置。

    其最主要的邏輯實現在getResource方法中,該方法首先判斷傳入的location是否以”classpath:”開頭,如果是,則創建ClassPathResource(移除”classpath:”前綴),否則嘗試創建UrlResource,如果當前location沒有定義URL的協議(即以”file:””zip:”等開頭,比如使用相對路徑”resources/META-INF/MENIFEST.MF),則創建UrlResource會拋出MalformedURLException,此時調用getResourceByPath()方法獲取Resource實例。getResourceByPath()方法默認返回ClassPathContextResource實例,在FileSystemResourceLoader中有不同實現。

    public Resource getResource(String location) {

        Assert.notNull(location, "Location must not be null");

        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);

            }

        }

    }

    protected Resource getResourceByPath(String path) {

        return new ClassPathContextResource(path, getClassLoader());

    }

    FileSystemResourceLoader

    FileSystemResourceLoader繼承自DefaultResourceLoader,它的getResource方法的實現邏輯和DefaultResourceLoader相同,不同的是它實現了自己的getResourceByPath方法,即當UrlResource創建失敗時,它會使用FileSystemContextResource實例而不是ClassPathContextResource

    protected Resource getResourceByPath(String path) {

        if (path != null && path.startsWith("/")) {

            path = path.substring(1);

        }

        return new FileSystemContextResource(path);

    }

    使用該類時要特別注意的一點:即使location”/”開頭,資源的查找還是相對于VM啟動時的相對路徑而不是絕對路徑(從以上代碼片段也可以看出,它會先截去開頭的”/”),這個和Servlet Container保持一致。如果需要使用絕對路徑,需要添加”file:”前綴。

    ServletContextResourceLoader

    ServletContextResourceLoader類繼承自DefaultResourceLoader,和FileSystemResourceLoader一樣,它的getResource方法的實現邏輯和DefaultResourceLoader相同,不同的是它實現了自己的getResourceByPath方法,即當UrlResource創建失敗時,它會使用ServletContextResource實例:

    protected Resource getResourceByPath(String path) {

        return new ServletContextResource(this.servletContext, path);

    }

    這里的path即使以”/”開頭,也是相對ServletContext的路徑,而不是絕對路徑,要使用絕對路徑,需要添加”file:”前綴。

    PathMatchingResourcePatternResolver

    PathMatchingResourcePatternResolver類實現了ResourcePatternResolver接口,它包含了對ResourceLoader接口的引用,在對繼承自ResourceLoader接口的方法的實現會代理給該引用,同時在getResources()方法實現中,當找到一個匹配的資源location時,可以使用該引用解析成Resource實例。默認使用DefaultResourceLoader類,用戶可以使用構造函數傳入自定義的ResourceLoader

    PathMatchingResourcePatternResolver還包含了一個對PathMatcher接口的引用,該接口基于路徑字符串實現匹配處理,如判斷一個路徑字符串是否包含通配符(’*’’?’),判斷給定的path是否匹配給定的pattern等。Spring提供了AntPathMatcherPathMatcher的默認實現,表達該PathMatcher是采用Ant風格的實現。其中PathMatcher的接口定義如下:

    public interface PathMatcher {

        boolean isPattern(String path);

        boolean match(String pattern, String path);

        boolean matchStart(String pattern, String path);

        String extractPathWithinPattern(String pattern, String path);

    }

    isPattern(String path)

    判斷path是否是一個pattern,即判斷path是否包含通配符:

    public boolean isPattern(String path) {

        return (path.indexOf('*') != -1 || path.indexOf('?') != -1);

    }

    match(String pattern, String path)

    判斷給定path是否可以匹配給定pattern

    matchStart(String pattern, String path)

    判斷給定path是否可以匹配給定pattern,該方法不同于match,它只是做部分匹配,即當發現給定path匹配給定path的可能性比較大時,即返回true。在PathMatchingResourcePatternResolver中,可以先使用它確定需要全面搜索的范圍,然后在這個比較小的范圍內再找出所有的資源文件全路徑做匹配運算。

    AntPathMatcher中,都使用doMatch方法實現,match方法的fullMatchtrue,而matchStartfullMatchfalse

    protected boolean doMatch(String pattern, String path, boolean fullMatch)

    doMatch的基本算法如下:

    1.       檢查patternpath是否都以”/”開頭或者都不是以”/”開頭,否則,返回false

    2.       patternpath都以”/”為分隔符,分割成兩個字符串數組pattArraypathArray

    3.       從頭遍歷兩個字符串數組,如果遇到兩給字符串不匹配(兩個字符串的匹配算法再下面介紹),返回false,否則,直到遇到pattArray中的”**”字符串,或pattArraypathArray中有一個遍歷完。

    4.       如果pattArray遍歷完:

    a)         pathArray也遍歷完,并且patternpath都以”/”結尾或都不以”/”,返回true,否則返回false

    b)         pattArray沒有遍歷完,但fullMatchfalse,返回true

    c)         pattArray只剩最后一個”*”,同時path”/”結尾,返回true

    d)         pattArray剩下的字符串都是”**”,返回true,否則返回false

    5.       如果pathArray沒有遍歷完,而pattArray遍歷完了,返回false

    6.       如果pathArraypattArray都沒有遍歷完,fullMatchfalse,而且pattArray下一個字符串為”**”時,返回true

    7.       從后開始遍歷pathArraypattArray,如果遇到兩個字符串不匹配,返回false,否則,直到遇到pattArray中的”**”字符串,或pathArraypattArray中有一個和之前的遍歷索引相遇。

    8.       如果是因為pathArray與之前的遍歷索引相遇,此時,如果沒有遍歷完的pattArray所有字符串都是”**”,則返回true,否則,返回false

    9.       如果pathArraypattArray中間都沒有遍歷完:

    a)         去除pattArray中相鄰的”**”字符串,并找到其下一個”**”字符串,其索引號為pattIdxTmp,他們的距離即為s

    b)         從剩下的pathArray中的第i個元素向后查找s個元素,如果找到所有s個元素都匹配,則這次查找成功,記itemp,如果沒有找到這樣的s個元素,返回false

    c)         pattArray的起始索引設置為pattIdxTmp,將pathArray的索引號設置為temp+s,繼續查找,直到pattArraypathArray遍歷完。

    10.   如果pattArray沒有遍歷完,但剩下的元素都是”**”,返回true,否則返回false

    對路徑字符串數組中的字符串匹配算法如下:

    1.       pattern為模式字符串,str為要匹配的字符串,將兩個字符串轉換成兩個字符數組pattArraystrArray

    2.       遍歷pattArray直到遇到’*’字符。

    3.       如果pattArray中不存在’*’字符,則只有在pattArraystrArray的長度相同兩個字符數組中所有元素都相同,其中pattArray中的’?’字符可以匹配strArray中的任何一個字符,否則,返回false

    4.       如果pattArray只包含一個’*’字符,返回true

    5.       遍歷pattArraystrArray直到pattArray遇到’*’字符或strArray遍歷完,如果存在不匹配的字符,返回false

    6.       如果因為strArray遍歷完成,而pattArray剩下的字符都是’*’,返回true,否則返回false

    7.       從末尾開始遍歷pattArraystrArray,直到pattArray遇到’*’字符,或strArray遇到之前的遍歷索引,中間如果遇到不匹配字符,返回false

    8.       如果strArray遍歷完,而剩下的pattArray字符都是’*’字符,返回true,否則返回false

    9.       如果pattArraystrArray都沒有遍歷完(類似之前的算法):

    a)         去除pattArray相鄰的’*’字符,查找下一個’*’字符,記其索引號為pattIdxTmp,兩個’*’字符的相隔距離為s

    b)         從剩下的strArray中的第i個元素向后查找s個元素,如果有找到所有s個元素都匹配,則這次查找成功,記itemp,如果沒有到這樣的s個元素,返回false

    c)         pattArray的起始索引設置為pattIdxTmpstrArray的起始索引設置為temp+s,繼續查找,直到pattArraystrArray遍歷完。

    10.   如果pattArray沒有遍歷完,但剩下的元素都是’*’,返回true,否則返回false

    String extractPathWithinPattern(String pattern, String path)

    去除path中和pattern相同的字符串,只保留匹配的字符串。比如如果pattern”/doc/csv/*.htm”,而path”/doc/csv/commit.htm”,則該方法的返回值為commit.htm。該方法默認patternpath已經匹配成功,因而算法比較簡單:

    ’/’分割patternpath為兩個字符串數組pattArraypathArray,遍歷pattArray,如果該字符串包含’*’’?’字符,則并且pathArray的長度大于當前索引號,則將該字符串添加到結果中。

    遍歷完pattArray后,如果pathArray長度大于pattArray,則將剩下的pathArray都添加到結果字符串中。

    最后返回該字符串。

    不過也正是因為該算法實現比較簡單,因而它的結果貌似不那么準確,比如pattern的值為:/com/**/levin/**/commit.html,而path的值為:/com/citi/cva/levin/html/commit.html,其返回結果為:citi/levin/commit.html

    現在言歸正傳,看一下PathMatchingResourcePatternResolver中的getResources方法的實現:

    public Resource[] getResources(String locationPattern) throws IOException {

        Assert.notNull(locationPattern, "Location pattern must not be null");

        if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {

            // a class path resource (multiple resources for same name possible)

            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

                return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));

            }

        }

        else {

            // Only look for a pattern after a prefix here

            // (to not get fooled by a pattern symbol in a strange prefix).

            int prefixEnd = locationPattern.indexOf(":") + 1;

            if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {

                // a file pattern

                return findPathMatchingResources(locationPattern);

            }

            else {

                // a single resource with the given name

                return new Resource[] {getResourceLoader().getResource(locationPattern)};

            }

        }

    }

    classpath下的資源,相同名字的資源可能存在多個,如果使用”classpath*:”作為前綴,表明需要找到classpath下所有該名字資源,因而需要調用findClassPathResources方法查找classpath下所有該名稱的Resource,對非classpath下的資源,對于不存在模式字符的location,一般認為一個location對應一個資源,因而直接調用ResourceLoader.getResource()方法即可(對classpath下沒有以”classpath*:”開頭的location也適用)。

    findClassPathResources方法實現相對比較簡單:

    適用ClassLoader.getResources()方法,遍歷結果URL集合,將每個結果適用UrlResource封裝,最后組成一個Resource數組返回即可。

    對包含模式匹配字符的location來說,需要調用findPathMatchingResources方法:

    protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {

        String rootDirPath = determineRootDir(locationPattern);

        String subPattern = locationPattern.substring(rootDirPath.length());

        Resource[] rootDirResources = getResources(rootDirPath);

        Set result = new LinkedHashSet(16);

        for (int i = 0; i < rootDirResources.length; i++) {

            Resource rootDirResource = resolveRootDirResource(rootDirResources[i]);

            if (isJarResource(rootDirResource)) {

                result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));

            }

            else {

                result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));

            }

        }

        if (logger.isDebugEnabled()) {

            logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);

        }

        return (Resource[]) result.toArray(new Resource[result.size()]);

    }

    1.       determinRootDir()方法返回locationPattern中最長的沒有出現模式匹配字符的路徑

    2.       subPattern則表示rootDirPath之后的包含模式匹配字符的路徑信pattern

    3.       使用getResources()獲取rootDirPath下的所有資源數組。

    4.       遍歷這個數組。

    a)         jar中的資源,使用doFindPathMatchingJarResources()方法來查找和匹配。

    b)         對非jar中資源,使用doFindPathMatchingFileResources()方法來查找和匹配。

    doFindPathMatchingJarResources()實現:

    1.       計算當前ResourceJar文件中的根路徑rootEntryPath

    2.       遍歷Jar文件中所有entry,如果當前entry名以rootEntryPath開頭,并且之后的路徑信息和之前從patternLocation中截取出的subPattern使用PathMatcher匹配,若匹配成功,則調用rootDirResource.createRelative方法創建一個Resource,將新創建的Resource添加入結果集中。

    doFindPathMatchingFileResources()實現:

    1.       獲取要查找資源的根路徑(根路徑全名)

    2.       遞歸獲得根路徑下的所有資源,使用PathMatcher匹配,如果匹配成功,則創建FileSystemResource,并將其加入到結果集中。在遞歸進入一個目錄前首先調用PathMatcher.matchStart()方法,以先簡單的判斷是否需要遞歸進去,以提升性能。

    protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set result) throws IOException {

        if (logger.isDebugEnabled()) {

            logger.debug("Searching directory [" + dir.getAbsolutePath() +

                    "] for files matching pattern [" + fullPattern + "]");

        }

        File[] dirContents = dir.listFiles();

        if (dirContents == null) {

            throw new IOException("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");

        }

        for (int i = 0; i < dirContents.length; i++) {

            File content = dirContents[i];

            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");

            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {

                doRetrieveMatchingFiles(fullPattern, content, result);

            }

            if (getPathMatcher().match(fullPattern, currPath)) {

                result.add(content);

            }

        }

    }

    最后,需要注意的是,由于ClassLoader.getResources()方法存在的限制,當傳入一個空字符串時,它只能從classpath的文件目錄下查找,而不會從Jar文件的根目錄下查找,因而對”classpath*:”前綴的資源來說,找不到Jar根路徑下的資源。即如果我們有以下定義:”classpath*:*.xml”,如果只有在Jar文件的根目錄下存在*.xml文件,那么這個pattern將返回空的Resource數組。解決方法是不要再Jar文件根目錄中放文件,可以將這些文件放到Jar文件中的resourcesconfig等目錄下去。并且也不要在”classpath*:”之后加一些通配符,如”classpath*:**/*Enum.class”,至少在”classpath*:”后加入一個不存在通配符的路徑名。

    ServletContextResourcePatternResolver

    ServletContextResourcePatternResolver類繼承自PathMatchingResourcePatternResolver類,它重寫了父類的文件查找邏輯,即對ServletContextResource資源使用ServletContext.getResourcePaths()方法來查找參數目錄下的文件,而不是File.listFiles()方法:

    protected Set doFindPathMatchingFileResources(Resource rootDirResource, String subPattern) throws IOException {

        if (rootDirResource instanceof ServletContextResource) {

            ServletContextResource scResource = (ServletContextResource) rootDirResource;

            ServletContext sc = scResource.getServletContext();

            String fullPattern = scResource.getPath() + subPattern;

            Set result = new LinkedHashSet(8);

            doRetrieveMatchingServletContextResources(sc, fullPattern, scResource.getPath(), result);

            return result;

        }

        else {

            return super.doFindPathMatchingFileResources(rootDirResource, subPattern);

        }

    }

    AbstractApplicationContextResourcePatternResolver接口的實現

    AbstractApplicationContext中,對ResourcePatternResolver的實現只是簡單的將getResources()方法的實現代理給resourcePatternResolver字段,而該字段默認在AbstractApplicationContext創建時新建一個PathMatchingResourcePatternResolver實例:

    public AbstractApplicationContext(ApplicationContext parent) {

        this.parent = parent;

        this.resourcePatternResolver = getResourcePatternResolver();

    }

    protected ResourcePatternResolver getResourcePatternResolver() {

        return new PathMatchingResourcePatternResolver(this);

    }

    public Resource[] getResources(String locationPattern) throws IOException {

        return this.resourcePatternResolver.getResources(locationPattern);

    }

    posted on 2012-12-01 23:21 DLevin 閱讀(15101) 評論(2)  編輯  收藏 所屬分類: Spring

    FeedBack:
    # re: 深入Spring IOC源碼之ResourceLoader
    2015-11-30 15:48 | nn
    我一直不太理解java 的加載機制,不知道為什么,我說的理解 徹底透徹的理解不是簡簡單單的應用,是不是要學習 java虛擬機呢,  回復  更多評論
      
    # re: 深入Spring IOC源碼之ResourceLoader
    2015-12-01 10:06 | DLevin
    @nn
    看你要理解到什么樣的程度了,可以先讀這本書:深入Java虛擬機,或者:深入理解Java虛擬機,個人感覺從理論的角度,第一本更好,從實戰的角度,第二本也還不錯。如果你需要再深入的話,那就去讀虛擬機的源碼好了。。。。  回復  更多評論
      

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲激情电影在线| 久久国产免费观看精品| 亚洲精品成人久久| 国产精品亚洲w码日韩中文| 久久久久久99av无码免费网站| 国内精品免费在线观看| 日韩精品无码永久免费网站| 99999久久久久久亚洲| 亚洲人成电影在线天堂| 亚洲人成人无码网www电影首页| 国产三级免费电影| 97无码免费人妻超级碰碰夜夜| 国产精成人品日日拍夜夜免费 | 18禁美女黄网站色大片免费观看 | 在线观看片免费人成视频无码| 国产大陆亚洲精品国产| 在线精品亚洲一区二区| 亚洲午夜成激人情在线影院| 亚洲天天做日日做天天欢毛片| 亚洲精品无码成人片久久 | 抽搐一进一出gif免费视频| 免费大片av手机看片高清| 欧美激情综合亚洲一二区| 亚洲一区二区三区在线观看网站| 亚洲伊人久久精品| 亚洲成av人片不卡无码| 亚洲成a人片毛片在线| 亚洲成a人片7777| 亚洲国产成人精品激情| youjizz亚洲| 精品丝袜国产自在线拍亚洲| 亚洲精品伊人久久久久| 亚洲愉拍一区二区三区| 伊人久久五月丁香综合中文亚洲| 亚洲乱码在线观看| 亚洲第一街区偷拍街拍| 国产午夜亚洲精品不卡免下载 | 久久久久国色AV免费看图片| 大地资源二在线观看免费高清| 18禁成年无码免费网站无遮挡| 免费黄色网址网站|