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。 ProtocolResolver 接口,仅有一个方法 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.jsp
com/*.jsp
— 匹配com目录线面所有的l .jsp
文件
com/**/test.jsp
—匹配com目录还有子目录下所有的 test.jsp
org/springframework/**/*.jsp
— 匹配 org/springframework
目录和子目录下所有的 .jsp
org/**/servlet/bla.jsp
— 匹配org/springframework/servlet/bla.jsp
、org/springframework/testing/servlet/bla.jsp
、 org/servlet/bla.jsp
com/{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-*.xml
classpath*:test/
classpath*:test/aa/spring-*.xml
classpath*: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)
。