上善若水
          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》中已經(jīng)詳細(xì)介紹了SpringResource的抽象,Resource接口有很多實(shí)現(xiàn)類,我們當(dāng)然可以使用各自的構(gòu)造函數(shù)創(chuàng)建符合需求的Resource實(shí)例,然而Spring提供了ResourceLoader接口用于實(shí)現(xiàn)不同的Resource加載策略,即將不同Resource實(shí)例的創(chuàng)建交給ResourceLoader來計(jì)算。

          public interface ResourceLoader {

              //classpath

              String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

              Resource getResource(String location);

              ClassLoader getClassLoader();

          }

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

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

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

          3.       相對路徑資源,如”WEB-INF/test.dat”,此時(shí)返回的Resource實(shí)例根據(jù)實(shí)現(xiàn)不同而不同。

          ResourceLoader接口還提供了getClassLoader()方法,在加載classpath下的資源時(shí)作為參數(shù)傳入ClassPathResource。將ClassLoader暴露出來,對于想要獲取ResourceLoader使用的ClassLoader用戶來說,可以直接調(diào)用getClassLoader()方法獲得,而不是依賴于Thread Context ClassLoader,因?yàn)橛行r(shí)候ResourceLoader內(nèi)部使用自定義的ClassLoader

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

          public interface ResourcePatternResolver extends ResourceLoader {

              String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

              Resource[] getResources(String locationPattern) throws IOException;

          }

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

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


          DefaultResourceLoader

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

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

          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方法的實(shí)現(xiàn)邏輯和DefaultResourceLoader相同,不同的是它實(shí)現(xiàn)了自己的getResourceByPath方法,即當(dāng)UrlResource創(chuàng)建失敗時(shí),它會使用FileSystemContextResource實(shí)例而不是ClassPathContextResource

          protected Resource getResourceByPath(String path) {

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

                  path = path.substring(1);

              }

              return new FileSystemContextResource(path);

          }

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

          ServletContextResourceLoader

          ServletContextResourceLoader類繼承自DefaultResourceLoader,和FileSystemResourceLoader一樣,它的getResource方法的實(shí)現(xiàn)邏輯和DefaultResourceLoader相同,不同的是它實(shí)現(xiàn)了自己的getResourceByPath方法,即當(dāng)UrlResource創(chuàng)建失敗時(shí),它會使用ServletContextResource實(shí)例:

          protected Resource getResourceByPath(String path) {

              return new ServletContextResource(this.servletContext, path);

          }

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

          PathMatchingResourcePatternResolver

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

          PathMatchingResourcePatternResolver還包含了一個對PathMatcher接口的引用,該接口基于路徑字符串實(shí)現(xiàn)匹配處理,如判斷一個路徑字符串是否包含通配符(’*’’?’),判斷給定的path是否匹配給定的pattern等。Spring提供了AntPathMatcherPathMatcher的默認(rèn)實(shí)現(xiàn),表達(dá)該PathMatcher是采用Ant風(fēng)格的實(shí)現(xiàn)。其中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,它只是做部分匹配,即當(dāng)發(fā)現(xiàn)給定path匹配給定path的可能性比較大時(shí),即返回true。在PathMatchingResourcePatternResolver中,可以先使用它確定需要全面搜索的范圍,然后在這個比較小的范圍內(nèi)再找出所有的資源文件全路徑做匹配運(yùn)算。

          AntPathMatcher中,都使用doMatch方法實(shí)現(xiàn),match方法的fullMatchtrue,而matchStartfullMatchfalse

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

          doMatch的基本算法如下:

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

          2.       patternpath都以”/”為分隔符,分割成兩個字符串?dāng)?shù)組pattArraypathArray

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

          4.       如果pattArray遍歷完:

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

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

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

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

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

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

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

          8.       如果是因?yàn)?/span>pathArray與之前的遍歷索引相遇,此時(shí),如果沒有遍歷完的pattArray所有字符串都是”**”,則返回true,否則,返回false

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

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

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

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

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

          對路徑字符串?dāng)?shù)組中的字符串匹配算法如下:

          1.       pattern為模式字符串,str為要匹配的字符串,將兩個字符串轉(zhuǎn)換成兩個字符數(shù)組pattArraystrArray

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

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

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

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

          6.       如果因?yàn)?/span>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的起始索引設(shè)置為pattIdxTmpstrArray的起始索引設(shè)置為temp+s,繼續(xù)查找,直到pattArraystrArray遍歷完。

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

          String extractPathWithinPattern(String pattern, String path)

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

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

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

          最后返回該字符串。

          不過也正是因?yàn)樵撍惴▽?shí)現(xiàn)比較簡單,因而它的結(jié)果貌似不那么準(zhǔn)確,比如pattern的值為:/com/**/levin/**/commit.html,而path的值為:/com/citi/cva/levin/html/commit.html,其返回結(jié)果為:citi/levin/commit.html

          現(xiàn)在言歸正傳,看一下PathMatchingResourcePatternResolver中的getResources方法的實(shí)現(xiàn):

          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下所有該名字資源,因而需要調(diào)用findClassPathResources方法查找classpath下所有該名稱的Resource,對非classpath下的資源,對于不存在模式字符的location,一般認(rèn)為一個location對應(yīng)一個資源,因而直接調(diào)用ResourceLoader.getResource()方法即可(對classpath下沒有以”classpath*:”開頭的location也適用)。

          findClassPathResources方法實(shí)現(xiàn)相對比較簡單:

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

          對包含模式匹配字符的location來說,需要調(diào)用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中最長的沒有出現(xiàn)模式匹配字符的路徑

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

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

          4.       遍歷這個數(shù)組。

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

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

          doFindPathMatchingJarResources()實(shí)現(xiàn):

          1.       計(jì)算當(dāng)前ResourceJar文件中的根路徑rootEntryPath

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

          doFindPathMatchingFileResources()實(shí)現(xiàn):

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

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

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

          ServletContextResourcePatternResolver

          ServletContextResourcePatternResolver類繼承自PathMatchingResourcePatternResolver類,它重寫了父類的文件查找邏輯,即對ServletContextResource資源使用ServletContext.getResourcePaths()方法來查找參數(shù)目錄下的文件,而不是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接口的實(shí)現(xiàn)

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

          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 閱讀(15107) 評論(2)  編輯  收藏 所屬分類: Spring

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

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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 石嘴山市| 镇巴县| 克什克腾旗| 泾川县| 临汾市| 佛冈县| 仪陇县| 方山县| 东阿县| 桂林市| 贵南县| 和硕县| 柏乡县| 洮南市| 赣州市| 龙泉市| 神池县| 射阳县| 喀喇沁旗| 庆云县| 盐城市| 临泉县| 勃利县| 友谊县| 双鸭山市| 加查县| 大安市| 米林县| 始兴县| 德惠市| 咸阳市| 临海市| 来宾市| 闽侯县| 长寿区| 依安县| 都昌县| 陆良县| 阿勒泰市| 丹东市| 六安市|