java中有一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Resource Locator),但是我们知道它的实现基本只限于网络形式发布的资源的查找和定位。虽然可以通过注册特定的处理器来扩展处理不同的地址前缀,但是这通常比较麻烦,而且不易于使用。另外,实际上资源的定义比较广泛,除了网络形式的资源,还有以二进制形式存在的、以文件形式存在的、以字节流形式存在的等等。而且它可以存在于任何场所,比如网络、文件系统、应用程序中。所以 java.net.URL 的局限性迫使Spring必须实现自己的资源加载策略,该资源加载策略需要满足如下要求:
职能划分清楚 :资源的定义和资源的加载应该要有一个清晰的界限;统一的抽象 :统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎样的处理,应该由抽象资源接口来界定。 
org.springframework.core.io.Resource 为Spring框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource接口。作为所有资源的统一抽象,Source定义了一些通用的方法,由子类 AbstractResource 提供统一的默认实现。定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public  interface  Resource  extends  InputStreamSource  {         boolean  exists () ;          default  boolean  isReadable ()  {         return  true ;     }          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 () ; } 
从上图可以看到,Resource 根据资源的不同类型提供不同的具体实现,如下:
FileSystemResource:对 java.io.File 类型资源的封装,只要是File类能操作的资源,基本都可以使用FileSystemResource来代替。支持文件和 URL 的形式,实现 WritableResource 接口,且从Spring Framework 5.0开始,FileSystemResource 使用NIO API进行读/写交互 
ByteArrayResource:对字节数组提供的数据的封装。如果通过InputStream形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。 
UrlResource:对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。 
ClassPathResource:class path类型资源的实现。使用给定的ClassLoader或者给定的Class来加载资源。 
InputStreamResource:将给定的InputStream作为一种资源的Resource的实现类。 
 
AbstractResource为Resource接口的默认实现,它实现了Resource接口的大部分的公共实现,作为Resource接口中的重中之重,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 public  abstract  class  AbstractResource  implements  Resource  {         @Override      public  boolean  exists ()  {         try  {             return  getFile().exists();         }         catch  (IOException ex) {                          try  {                 InputStream  is  =  getInputStream();                 is.close();                 return  true ;             }             catch  (Throwable isEx) {                 return  false ;             }         }     }          @Override      public  boolean  isReadable ()  {         return  true ;     }          @Override      public  boolean  isOpen ()  {         return  false ;     }          @Override      public  boolean  isFile ()  {         return  false ;     }          @Override      public  URL getURL ()  throws  IOException {         throw  new  FileNotFoundException (getDescription() + " cannot be resolved to URL" );     }          @Override      public  URI getURI ()  throws  IOException {         URL  url  =  getURL();         try  {             return  ResourceUtils.toURI(url);         }         catch  (URISyntaxException ex) {             throw  new  NestedIOException ("Invalid URI ["  + url + "]" , ex);         }     }          @Override      public  File getFile ()  throws  IOException {         throw  new  FileNotFoundException (getDescription() + " cannot be resolved to absolute file path" );     }          @Override      public  ReadableByteChannel readableChannel ()  throws  IOException {         return  Channels.newChannel(getInputStream());     }          @Override      public  long  contentLength ()  throws  IOException {         InputStream  is  =  getInputStream();         try  {             long  size  =  0 ;             byte [] buf = new  byte [255 ];             int  read;             while  ((read = is.read(buf)) != -1 ) {                 size += read;             }             return  size;         }         finally  {             try  {                 is.close();             }             catch  (IOException ex) {             }         }     }          @Override      public  long  lastModified ()  throws  IOException {         long  lastModified  =  getFileForLastModifiedCheck().lastModified();         if  (lastModified == 0L ) {             throw  new  FileNotFoundException (getDescription() +                     " cannot be resolved in the file system for resolving its last-modified timestamp" );         }         return  lastModified;     }     protected  File getFileForLastModifiedCheck ()  throws  IOException {         return  getFile();     }          @Override      public  Resource createRelative (String relativePath)  throws  IOException {         throw  new  FileNotFoundException ("Cannot create a relative resource for "  + getDescription());     }          @Override      @Nullable      public  String getFilename ()  {         return  null ;     }          @Override      public  String toString ()  {         return  getDescription();     }     @Override      public  boolean  equals (Object obj)  {         return  (obj == this  ||             (obj instanceof  Resource && ((Resource) obj).getDescription().equals(getDescription())));     }     @Override      public  int  hashCode ()  {         return  getDescription().hashCode();     } } 
如果我们想要实现自定义的 Resource,记住不要实现 Resource 接口,而应该继承 AbstractResource 抽象类,然后根据当前的具体资源特性覆盖相应的方法即可。
统一资源定位:ResourceLoader 一开始就说了Spring将资源的定义和资源的加载区分开了,Resource定义了统一的资源,那资源的加载则由ResourceLoader来统一定义。
org.springframework.core.io.ResourceLoader 为Spring资源加载的统一抽象,具体的资源加载则由相应的实现类来完成,所以我们可以将 ResourceLoader 称作为统一资源定位器。其定义如下:
1 2 3 4 5 6 7 public  interface  ResourceLoader  {    String  CLASSPATH_URL_PREFIX  =  ResourceUtils.CLASSPATH_URL_PREFIX;     Resource getResource (String location) ;     ClassLoader getClassLoader () ; } 
ResourceLoader 接口提供两个方法:getResource()、getClassLoader()。
getResource()根据所提供资源的路径location返回Resource实例,但是它不确保该 Resource 一定存在,需要调用 Resource.exist()方法判断。该方法支持以下模式的资源加载:
URL位置资源,如”file:C:/test.dat” 
ClassPath位置资源,如”classpath:test.dat” 
相对路径资源,如”WEB-INF/test.dat”,此时返回的Resource实例根据实现不同而不同 
 
该方法的主要实现是在其子类DefaultResourceLoader中实现,具体过程我们在分析DefaultResourceLoader时做详细说明。
getClassLoader() :获取ResourceLoader对象使用的ClassLoader,在分析Resource时,提到了一个类ClassPathResource ,这个类是可以根据指定的ClassLoader来加载资源的。
作为Spring统一的资源加载器,它提供了统一的抽象,具体的实现则由相应的子类来负责实现,其类的类结构图如下:
DefaultResourceLoader DefaultResourceLoader 是 ResourceLoader 的默认实现,它接收 ClassLoader 作为构造函数的参数或者使用不带参数的构造函数,在使用不带参数的构造函数时,使用的 ClassLoader 为默认的 ClassLoader(一般为Thread.currentThread().getContextClassLoader()),可以通过 ClassUtils.getDefaultClassLoader()获取。当然也可以调用 setClassLoader()方法进行后续设置。如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public  DefaultResourceLoader ()  {       this .classLoader = ClassUtils.getDefaultClassLoader();    }    public  DefaultResourceLoader (@Nullable  ClassLoader classLoader)  {        this .classLoader = classLoader;    }    public  void  setClassLoader (@Nullable  ClassLoader classLoader)  {        this .classLoader = classLoader;    }    @Override     @Nullable     public  ClassLoader getClassLoader ()  {        return  (this .classLoader != null  ? this .classLoader :                 ClassUtils.getDefaultClassLoader());    } 
ResourceLoader 中最核心的方法为 getResource(),它根据提供的 location 返回相应的 Resource,而 DefaultResourceLoader对该方法提供了核心实现(它的两个子类都没有提供覆盖该方法,所以可以断定ResourceLoader 的资源加载策略就封装 DefaultResourceLoader中),如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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  {                          URL  url  =  new  URL (location);             return  (ResourceUtils.isFileURL(url) ?                      new  FileUrlResource (url) : new  UrlResource (url));         }         catch  (MalformedURLException ex) {                          return  getResourceByPath(location);         }     } } 
首先通过 ProtocolResolver 来加载资源,成功返回 Resource,否则调用如下逻辑:
首先委托给ProtocolResolver来进行解析,这个下面会说明。 
 
若 location 以/ 开头,则调用 getResourceByPath()构造 ClassPathContextResource 类型资源并返回。 
若 location 以 classpath: 开头,则构造 ClassPathResource 类型资源并返回,在构造该资源时,通过 getClassLoader()获取当前的 ClassLoader。 
构造 URL ,尝试通过它进行资源定位,若没有抛出 MalformedURLException 异常,则判断是否为 FileURL , 如果是则构造 FileUrlResource 类型资源,否则构造 UrlResource。若在加载过程中抛出 MalformedURLException 异常,则委派 getResourceByPath() 实现资源定位加载。 
 
这里有一点要注意的是,返回了resource不代表这个资源存在,和前面介绍resource一样,需要通过exitsts来确认文件的存在。
ProtocolResolver ,用户自定义协议资源解决策略,作为 DefaultResourceLoader 的 SPI,它允许用户自定义资源加载协议,而不需要继承 ResourceLoader 的子类。在介绍 Resource 时,提到如果要实现自定义 Resource,我们只需要继承 DefaultResource 即可,但是有了 ProtocolResolver 后,我们不需要直接继承 DefaultResourceLoader,改为实现 ProtocolResolver 接口也可以实现自定义的 ResourceLoader。Resource resolve(String location, ResourceLoader resourceLoader),该方法接收两个参数:资源路径location,指定的加载器 ResourceLoader,返回为相应的 Resource 。在 Spring 中你会发现该接口并没有实现类,它需要用户自定义,自定义的 Resolver 如何加入 Spring 体系呢?调用 DefaultResourceLoader.addProtocolResolver() 即可,如下:
1 2 3 4 public  void  addProtocolResolver (ProtocolResolver resolver)  {    Assert.notNull(resolver, "ProtocolResolver must not be null" );     this .protocolResolvers.add(resolver); } 
下面是我写的一个简单ProtocolResolver的demo,演示了使用方式,如果你希望添加自定义的文件格式解析,可以向下面这样添加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20    public  static  void  main (String[] args)  {	DefaultResourceLoader  defaultResourceLoader  =  new  DefaultResourceLoader (); 	defaultResourceLoader.getProtocolResolvers().forEach(System.out::println); 	defaultResourceLoader.addProtocolResolver(new   			CustomerResourceProtocolResolver ()); 	System.out.println(defaultResourceLoader.getResource("/Users/zhangke/HTTP请求.jmx" ).exists()); } static  class  CustomerResourceProtocolResolver  implements  ProtocolResolver  {	@Override  	public  Resource resolve (String location, ResourceLoader resourceLoader)  { 		FileSystemResource  fileSystemResource  =  new  FileSystemResource (location); 		return  fileSystemResource; 	} } 
下面示例是演示 DefaultResourceLoader 加载资源的具体策略,代码如下(该示例参考《Spring 解密》 P89):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ResourceLoader  resourceLoader  =  new  DefaultResourceLoader ();Resource  fileResource1  =      resourceLoader.getResource("D:/Users/chenming673/Documents/spark.txt" ); System.out.println("fileResource1 is FileSystemResource:"                      + (fileResource1 instanceof  FileSystemResource)); Resource  fileResource2  =      resourceLoader.getResource("/Users/chenming673/Documents/spark.txt" ); System.out.println("fileResource2 is ClassPathResource:"                      + (fileResource2 instanceof  ClassPathResource)); Resource  urlResource1  =  resourceLoader			.getResource("file:/Users/chenming673/Documents/spark.txt" ); System.out.println("urlResource1 is UrlResource:"                      + (urlResource1 instanceof  UrlResource)); Resource  urlResource2  =  resourceLoader.getResource("http://www.baidu.com" );System.out.println("urlResource1 is urlResource:"                      + (urlResource2 instanceof   UrlResource)); 
运行结果:
1 2 3 4 fileResource1 is FileSystemResource:false fileResource2 is ClassPathResource:true urlResource1 is UrlResource:true urlResource1 is urlResource:true 
其实对于 fileResource1 我们更加希望是 FileSystemResource 资源类型,但是事与愿违,它是 ClassPathResource 类型。在getResource()资源加载策略中,我们知道 D:/Users/chenming673/Documents/spark.txt资源其实在该方法中没有相应的资源类型,那么它就会在抛出 MalformedURLException 异常时通过 getResourceByPath() 构造一个 ClassPathResource 类型的资源。而指定有协议前缀的资源路径,则通过 URL 就可以定义,所以返回的都是UrlResource类型。
FileSystemResourceLoader 从上面的示例我们看到,其实 DefaultResourceLoader 对getResourceByPath(String)方法处理其实不是很恰当,这个时候我们可以使用 FileSystemResourceLoader ,它继承 DefaultResourceLoader 且覆写了 getResourceByPath(String),使之从文件系统加载资源并以 FileSystemResource 类型返回,这样我们就可以得到想要的资源类型,如下:
1 2 3 4 5 6 7 @Override protected  Resource getResourceByPath (String path)  {    if  (path.startsWith("/" )) {         path = path.substring(1 );     }     return  new  FileSystemContextResource (path); } 
FileSystemContextResource 为 FileSystemResourceLoader 的内部类,它继承 FileSystemResource。
1 2 3 4 5 6 7 8 9 10 11 12 private  static  class  FileSystemContextResource      extends  FileSystemResource  implements  ContextResource  {     public  FileSystemContextResource (String path)  {         super (path);     }     @Override      public  String getPathWithinContext ()  {         return  getPath();     } } 
在构造器中也是调用 FileSystemResource 的构造方法来构造 FileSystemResource 的。
如果将上面的示例将 DefaultResourceLoader 改为 FileSystemContextResource ,则 fileResource1 则为 FileSystemResource。
ResourcePatternResolver ResourceLoader 的 Resource getResource(String location) 每次只能根据 location 返回一个 Resource,当需要加载多个资源时,我们除了多次调用 getResource() 外别无他法。ResourcePatternResolver 是 ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例,其定义如下:
1 2 3 4 5 public  interface  ResourcePatternResolver  extends  ResourceLoader  {    String  CLASSPATH_ALL_URL_PREFIX  =  "classpath*:" ;     Resource[] getResources(String locationPattern) throws  IOException; } 
ResourcePatternResolver 在 ResourceLoader 的基础上增加了 getResources(String locationPattern),以支持根据路径匹配模式返回多个 Resource 实例,同时也新增了一种新的协议前缀 classpath*:,该协议前缀由其子类负责实现。
PathMatchingResourcePatternResolver 为 ResourcePatternResolver 最常用的子类,它除了支持 ResourceLoader 和 ResourcePatternResolver 新增的 classpath*: 前缀外,还支持 Ant 风格的路径匹配模式。
这里先介绍什么是Ant风格的路径匹配模式,主要由以下规则,
?匹配一个字符 
*匹配0个或多个字符** 在路径中匹配0个或多个文件夹 
{spring;[a-z]+},匹配正则表达式[a-z]+并将结果存在名为spring变量中。 
 
例子
com/t?st.jsp —匹配 com/test.jsp 、 com/tast.jsp 、com/txst.jspcom/*.jsp — 匹配com目录线面所有的l .jsp 文件com/**/test.jsp —匹配com目录还有子目录下所有的 test.jsporg/springframework/**/*.jsp — 匹配 org/springframework 目录和子目录下所有的 .jsp org/**/servlet/bla.jsp — 匹配org/springframework/servlet/bla.jsp 、org/springframework/testing/servlet/bla.jsp 、 org/servlet/bla.jspcom/{filename:\\w+}.jsp 匹配 com/test.jsp 并且将值 test 付给 filename变量 
在spring经常使用AntPathMatcher来进行操作这些ant风格的路径匹配模式
小案例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public  class  AntPathMatcherStudy  {	public  static  void  main (String[] args)  { 		AntPathMatcher  antPathMatcher  =  new  AntPathMatcher (); 		 		System.out.println(antPathMatcher.match("com/t?st.jsp" , "com/test.jsp" )); 		System.out.println(antPathMatcher.match("com/t?st.jsp" , "com/tast.jsp" )); 		System.out.println(antPathMatcher.match("com/t?st.jsp" , "com/txst.jsp" )); 		 		System.out.println(antPathMatcher.match("com/*.jsp" , "com/test.jsp" )); 		System.out.println(antPathMatcher.match("com/*.jsp" , "com/tast.jsp" )); 		System.out.println(antPathMatcher.match("com/*.jsp" , "com/txst.jsp" )); 		 		System.out.println(antPathMatcher.match("org/springframework/**/*.jsp" , "org/springframework/com/test.jsp" )); 		System.out.println(antPathMatcher.match("org/springframework/**/*.jsp" , "org/springframework/test/com/tast.jsp" )); 		System.out.println(antPathMatcher.match("org/springframework/**/*.jsp" , "org/springframework/ddd/txst.jsp" )); 		 		System.out.println(antPathMatcher.extractUriTemplateVariables("/test/{spring:[a-z]+}/test.jsp" , "/test/test/test.jsp" )); 	} 
运行结果
1 2 3 4 5 6 7 8 9 10 true true true true true true true true true {spring=test} 
PathMatchingResourcePatternResolver 提供了三个构造方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 public  PathMatchingResourcePatternResolver ()  {    this .resourceLoader = new  DefaultResourceLoader (); } public  PathMatchingResourcePatternResolver (ResourceLoader resourceLoader)  {    Assert.notNull(resourceLoader, "ResourceLoader must not be null" );     this .resourceLoader = resourceLoader; } public  PathMatchingResourcePatternResolver (@Nullable  ClassLoader classLoader)  {    this .resourceLoader = new  DefaultResourceLoader (classLoader); } 
PathMatchingResourcePatternResolver 在实例化的时候,可以指定一个 ResourceLoader,如果不指定的话,它会在内部构造一个 DefaultResourceLoader。
Resource getResource(String location) 
1 2 3 4 @Override public  Resource getResource (String location)  {    return  getResourceLoader().getResource(location); } 
getResource() 方法直接委托给相应的 ResourceLoader 来实现,所以如果我们在实例化的 PathMatchingResourcePatternResolver 的时候,如果不知道 ResourceLoader ,那么在加载资源时,其实就是 DefaultResourceLoader 的过程。其实在下面介绍的 Resource[] getResources(String locationPattern) 也相同,只不过返回的资源时多个而已。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public  Resource[] getResources(String locationPattern) throws  IOException {       Assert.notNull(locationPattern, "Location pattern must not be null" );                if  (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {                        if  (getPathMatcher().isPattern(                locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {                return  findPathMatchingResources(locationPattern);            } else  {                                return  findAllClassPathResources(                    locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));            }        } else  {            int  prefixEnd  =  (locationPattern.startsWith("war:" ) ?              locationPattern.indexOf("*/" ) + 1  : locationPattern.indexOf(':' ) + 1 );                        if  (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {                return  findPathMatchingResources(locationPattern);            }else  {                                return  new  Resource [{getResourceLoader().getResource(locationPattern)};            }        }    } 
处理逻辑如下
下面就 findAllClassPathResources()、findAllClassPathResources() 做详细分析。
findAllClassPathResources() 
当 locationPattern 以 classpath*: 开头但是不包含通配符,则调用findAllClassPathResources() 方法加载资源。该方法返回 classes 路径下和所有 jar 包中的所有相匹配的资源。
1 2 3 4 5 6 7 8 9 10 11 12 protected  Resource[] findAllClassPathResources(String location) throws  IOException {       String  path  =  location;        if  (path.startsWith("/" )) {            path = path.substring(1 );        }        Set<Resource> result = doFindAllClassPathResources(path);        if  (logger.isDebugEnabled()) {            logger.debug("Resolved classpath location                           		["  + location + "] to resources "  + result);       }        return  result.toArray(new  Resource [0 ]);    } 
真正执行加载的是在 doFindAllClassPathResources()方法,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected  Set<Resource> doFindAllClassPathResources (String path)      throws  IOException {     Set<Resource> result = new  LinkedHashSet <>(16 );     ClassLoader  cl  =  getClassLoader();     Enumeration<URL> resourceUrls = (cl != null  ? cl.getResources(path) :                                       ClassLoader.getSystemResources(path));     while  (resourceUrls.hasMoreElements()) {         URL  url  =  resourceUrls.nextElement();         result.add(convertClassLoaderURL(url));     }     if  ("" .equals(path)) {         addAllClassLoaderJarRoots(cl, result);     }     return  result; } 
doFindAllClassPathResources() 根据 ClassLoader 加载路径下的所有资源。在加载资源过程中,如果在构造 PathMatchingResourcePatternResolver 实例的时候如果传入了 ClassLoader,则调用其 getResources(),否则调用ClassLoader.getSystemResources(path)。 ClassLoader.getResources()如下:
1 2 3 4 5 6 7 8 9 10 11 12 public  Enumeration<URL> getResources (String name)  throws  IOException {    @SuppressWarnings("unchecked")      Enumeration<URL>[] tmp = (Enumeration<URL>[]) new  Enumeration <?>[2 ];     if  (parent != null ) {         tmp[0 ] = parent.getResources(name);     } else  {         tmp[0 ] = getBootstrapResources(name);     }     tmp[1 ] = findResources(name);     return  new  CompoundEnumeration <>(tmp); } 
看到这里是不是就已经一目了然了?如果当前父类加载器不为 null,则通过父类向上迭代获取资源,否则调用 getBootstrapResources()。这里是不是特别熟悉。
若path为空时,则调用 addAllClassLoaderJarRoots()方法。该方法主要是加载路径下得所有 jar 包,方法较长也没有什么实际意义就不贴出来了。不过最后结果在每个jar后面都添加**!/**的原因是java官方规定的,可以看这个连接jar 中出现!/的原因 
通过上面的分析,我们知道 findAllClassPathResources() 其实就是利用 ClassLoader 来加载指定路径下的资源,不过它是在 class 路径下和 jar 包中。如果我们传入的路径为空或者 /,则会调用 addAllClassLoaderJarRoots() 方法加载所有的 jar 包。
findAllClassPathResources() 
当 locationPattern 以 classpath*: 开头且当中包含了通配符,则调用该方法进行资源加载。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 protected  Resource[] findPathMatchingResources(String locationPattern) throws  IOException {                 String  rootDirPath  =  determineRootDir(locationPattern);         String  subPattern  =  locationPattern.substring(rootDirPath.length());                  Resource[] rootDirResources = getResources(rootDirPath);         Set<Resource> result = new  LinkedHashSet <>(16 );         for  (Resource rootDirResource : rootDirResources) {             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);             }                          if  (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {                 result.addAll(                     VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl,                                                    subPattern, getPathMatcher()));             }                          else  if  (ResourceUtils.isJarURL(rootDirUrl) ||                       isJarResource(rootDirResource)) {                 result.addAll(doFindPathMatchingJarResources(rootDirResource,                                                               rootDirUrl, subPattern));             }             else  {                 result.addAll(doFindPathMatchingFileResources(rootDirResource,                                                                subPattern));             }         }         if  (logger.isDebugEnabled()) {             logger.debug("Resolved location pattern ["  + locationPattern + "] to                            resources "  + result);        }         return  result.toArray(new  Resource [0 ]);     } 
方法有点儿长,但是思路还是很清晰的,主要分两步:
确定目录,获取该目录下得所有资源 
在所获得的所有资源中进行迭代匹配获取我们想要的资源。 
 
在这个方法里面我们要关注两个方法,一个是 determineRootDir(),一个是 doFindPathMatchingFileResources()。
determineRootDir()主要是用于确定根路径,如下:
1 2 3 4 5 6 7 8 9 10 11 12 protected  String determineRootDir (String location)  {    int  prefixEnd  =  location.indexOf(':' ) + 1 ;     int  rootDirEnd  =  location.length();     while  (rootDirEnd > prefixEnd &&             getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {         rootDirEnd = location.lastIndexOf('/' , rootDirEnd - 2 ) + 1 ;     }     if  (rootDirEnd == 0 ) {         rootDirEnd = prefixEnd;     }     return  location.substring(0 , rootDirEnd); } 
该方法一定要给出一个确定的根目录。该根目录用于确定文件的匹配的起始点,将根目录位置的资源解析为 java.io.File并将其传递到 retrieveMatchingFiles(),其余为知用于模式匹配,找出我们所需要的资源。
确定根路径如下:
原路径 
确定根路径 
 
 
classpath*:test/cc*/spring-*.xmlclasspath*:test/ 
classpath*:test/aa/spring-*.xmlclasspath*:test/aa/ 
确定根路径后,则调用 getResources() 方法获取该路径下得所有资源,然后迭代资源获取符合条件的资源。
至此 Spring 整个资源记载过程已经分析完毕。下面简要总结下:
Spring 提供了 Resource 和 ResourceLoader 来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。 
AbstractResource 为 Resource 的默认实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。 
DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。 
DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 Resource getResource(String location) 也实现了 Resource[] getResources(String locationPattern)。