写在前面
什么是资源呢?最基本的本地磁盘上的一个文件,远端某服务器的一个图片,本地的一个jar包,本地的一个jar包内的内嵌jar包/内嵌class文件/内嵌properties文件,这些都是资源,为了能够满足各种资源的读取,spring定义了统一的资源加载策略。
1:Resource接口
该接口在spring的spring-core
模块中,完整类名org.springframework.core.io.Resource
,该接口要用来抽象资源的定义的,最终资源会封装为该接口类型的一个对象,然后使用该对象来操作资源,源码如下:
public interface Resource extends InputStreamSource {
// 判断文件是否存在
boolean exists();
// 判断文件是否可读,该方法是一个default方法,提供默认实现
default boolean isReadable() {
return exists();
}
// 判断文件句柄是否被打开,该方法是以default方法,提供默认实现
default boolean isOpen() {
return false;
}
// 判断是否为一个文件,该方法是一个default方法,提供默认实现
default boolean isFile() {
return false;
}
// 获取文件对应URL(如http://,jar://,ftp://,file://等)
URL getURL() throws IOException;
// 返回资源的URI
URI getURI() throws IOException;
// 获取文件对象
File getFile() throws IOException;
// 返回nio的ReadableByteChannel对象,该方法为default方法,直接使用
// Channels的静态方法newChannel创建
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(getInputStream());
}
// 返回文件的长度
long contentLength() throws IOException;
// 返回文件的上次修改时间
long lastModified() throws IOException;
// 根据当前资源的相对路径,创建其它资源的Resource
Resource createRelative(String relativePath) throws IOException;
// 获取文件的名称
@Nullable
String getFilename();
// 获取资源的描述
String getDescription();
}
其继承了接口org.springframework.core.io.InputStreamSource
,该接口只定义了一个获取输入流的方法,源码如下:
public interface InputStreamSource {
// 获取资源对应的输入流对象
InputStream getInputStream() throws IOException;
}
1.1:AbstractResource
该类是Resource接口的抽象实现类,全限定名是org.springframework.core.io.AbstractResource
,该类定义如下:
public abstract class AbstractResource implements Resource {
@Override
public boolean exists() {
// 直接根据File获取是否存在,如果无法正常获取则进入异常
// 尝试其它方式获取
try {
return getFile().exists();
}
catch (IOException ex) {
// 尝试通过InputStream获取,如果是能够close则返回true
// 否则进入异常返回false代表资源不存在
try {
getInputStream().close();
return true;
}
catch (Throwable isEx) {
return false;
}
}
}
@Override
public boolean isReadable() {
// 存在即可读
return exists();
}
@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");
}
// URL转URI
@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());
}
// 获取文件长度,其实就是文件内容的byte数
@Override
public long contentLength() throws IOException {
// 获取输入流
InputStream is = getInputStream();
try {
long size = 0;
byte[] buf = new byte[256];
int read;
// 通过while循环读取到byte数组中累加获取最终长度
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
}
finally {
try {
is.close();
}
catch (IOException ex) {
}
}
}
// 获取资源的最后修改时间
@Override
public long lastModified() throws IOException {
File fileToCheck = getFileForLastModifiedCheck();
long lastModified = fileToCheck.lastModified();
if (lastModified == 0L && !fileToCheck.exists()) {
throw new FileNotFoundException(getDescription() +
" cannot be resolved in the file system for checking its last-modified timestamp");
}
return lastModified;
}
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
// 根据当前资源的相对路径创建其它新的资源对象,直接抛出
// FileNotFoundException强制子类实现
@Override
public Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
// 获取文件名称,直接返回null,交给但不强制子类实现
@Override
@Nullable
public String getFilename() {
return null;
}
@Override
public boolean equals(Object other) {
return (this == other || (other instanceof Resource &&
((Resource) other).getDescription().equals(getDescription())));
}
@Override
public int hashCode() {
return getDescription().hashCode();
}
@Override
public String toString() {
return getDescription();
}
}
如果是我们想要自定义Resource直接继承该抽象类就可以了。
2:ResourceLoader接口
该接口在spring的spring-core模块中,限定名是org.springframework.core.io.ResourceLoader
。该接口用于定义资源的加载,其实内部是使用的Resource接口,源码不是很复杂,如下:
public interface ResourceLoader {
// public static final String CLASSPATH_URL_PREFIX = "classpath:";
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
// 根据位置获取资源对象
// 值可以是URL如file:c:/test.txt,classpath如classpath:resources.xml
// 也可以是相对路径,如WEB-INF/web.xml
Resource getResource(String location);
// 获取类加载器
@Nullable
ClassLoader getClassLoader();
}
2.1:DefaultResourceLoader
该类是ResourceLoader的默认实现类,注意是一个类,而不是抽象类,先来看一个使用的例子,代码:
@Test
public void testResourceLoader() throws Exception {
// 定义resourceloader
ResourceLoader resourceLoader = new DefaultResourceLoader();
// http://img.ewebweb.com/uploads/20191006/20/1570365161-shmEFlWfHU.jpg
String resourceLocation = "https://www.baidu.com/";
Resource resource = resourceLoader.getResource(resourceLocation);
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
System.out.println(resource.getURL());
System.out.println(resource.getURI());
InputStream inputStream = resource.getInputStream();
StringBuffer out = new StringBuffer();
byte[] b = new byte[4096];
for (int n; (n = inputStream.read(b)) != -1; ) {
out.append(new String(b, 0, n));
}
System.out.println(out);
}
测试:
2443
https://www.baidu.com/
https://www.baidu.com/
<!DOCTYPE html>
<!--STATUS OK--><html> ...snip...
title>百度一下,你就知道</title>
我们来就着例子分析下org.springframework.core.io.ResourceLoader#getResource
部分源码:
org.springframework.core.io.DefaultResourceLoader#getResource
@Override
publicResource getResource(String location) {
// 断言判断
Assert.notNull(location, "Location must not be null");
// <DefaultResourceLoader#getResource_1>
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
// 如果是以/开头则调用getResourceByPath方法,一般子类重载该方法就可以了
if (location.startsWith("/")) {
// <DefaultResourceLoader#getResource_2>
return getResourceByPath(location);
}
// 如果是以classpath开头,则返回ClassPathResouce对象
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 根据位置创建url对象
URL url = new URL(location);
// <DefaultResourceLoader#getResource_3>
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
<DefaultResourceLoader#getResource_1>
处本例中没有元素,后续会分析,这里暂时忽略,如下图:
这是协议扩展使用的。<DefaultResourceLoader#getResource_2>
处源码:
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
直接使用ClassPathContextResource类创建对象作为Resource使用。<DefaultResourceLoader#getResource_3>
是我们会真正执行到的代码位置,会首先通过ResourceUtils.isFileURL(url)
判断是否是文件地址,比如file://
就是文件地址,运行效果如下:
我们这里是https
协议,所以结果为false
,那么<DefaultResourceLoader#getResource_3>
处的最终执行的代码就是new UrlResource(url))
,即最终返回UrlResouce
作为Resource。
在<DefaultResourceLoader#getResource_1>
并没有ResourceResolver,这是spring留给我们的扩展口,可以通过实现org.springframework.core.io.ProtocolResolver
接口,然后通过方法org.springframework.core.io.DefaultResourceLoader#addProtocolResolver
添加,该方法源码如下:
org.springframework.core.io.DefaultResourceLoader#addProtocolResolver
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
// private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);
this.protocolResolvers.add(resolver);
}
下面我们来自定义一个schema为dongshidaddy:
的自定义协议解析器,在这之前我们先来看下ProtocolResolver的源码:
@FunctionalInterface
public interface ProtocolResolver {
@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);
}
注意到注解@FunctionalInterface
,该注解用于编译阶段,限制接口中只能有一个抽象方法,如下就是不可以的:
如下一个抽象,一个default是可以的:
只有一个抽象方法自然也是可以的:
接口中的方法Resource resolve(String location, ResourceLoader resourceLoader)
就是我们需要实现的唯一的方法,最终解析我们自定义的协议,返回Resource就可以了,如下自定义协议解析器:
public class DongshiDaddyProtocolResolver implements ProtocolResolver {
private static final String DONGSHIDADDY_SCHEMA="dongshidaddy:";
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (!location.startsWith(DONGSHIDADDY_SCHEMA))
return null;
String realPath = location.substring(13);
String classPath = "classpath:" + realPath;
return resourceLoader.getResource(classPath);
}
}
测试代码:
@Test
public void testSeftDefineProtocolResolver() throws Exception {
DefaultResourceLoader resourceLoader=new DefaultResourceLoader();
resourceLoader.addProtocolResolver(new DongshiDaddyProtocolResolver());
Resource resource = resourceLoader.getResource("dongshidaddy:test.txt");
InputStream inputStream = resource.getInputStream();
StringBuffer out = new StringBuffer();
byte[] b = new byte[4096];
for (int n; (n = inputStream.read(b)) != -1; ) {
out.append(new String(b, 0, n));
}
System.out.println(out);
}
测试的文件如下:
运行:
testing!!!
Process finished with exit code 0
如下debugorg.springframework.core.io.DefaultResourceLoader#getResource
:
因此可以说ProtocolResolver
是spring留给我们的扩展点
。
接下来我们单起篇幅来看下主要的ResouceLoader
都有哪些。
3:FileSystemResourceLoader
通过该资源加载器返回的资源类是FileSystemContextResource
。
3.1:测试代码
@Test
public void testFileSystemResourceLoader() {
String path = "d:\\test\\mystarter-1.0-SNAPSHOT.jar";
FileSystemResourceLoader fileSystemResourceLoader = new FileSystemResourceLoader();
Resource resource = fileSystemResourceLoader.getResource(path);
System.out.println("resource instanceof FileSystemResource: " + (resource instanceof FileSystemResource));
System.out.println("resource.getFilename() is: " + resource.getFilename());
}
首先执行如下代码:
org.springframework.core.io.DefaultResourceLoader#getResource
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
...snip...
else {
try {
...snip...
}
catch (MalformedURLException ex) {
// <2021-02-22 13:38>
return getResourceByPath(location);
}
}
}
会执行到代码<2021-02-22 13:38>
处,最终调用到方法org.springframework.core.io.FileSystemResourceLoader#getResourceByPath
,源码如下:
org.springframework.core.io.FileSystemResourceLoader#getResourceByPath
@Override
protected Resource getResourceByPath(String path) {
// 处理斜杠开头的情况,一般在linux环境,本例测试是windows环境
// 值是d:\\test\\mystarter-1.0-SNAPSHOT.jar,因此if为false
if (path.startsWith("/")) {
path = path.substring(1);
}
// <2021-02-22 10:41>,直接new一个FileSystemContextResource
return new FileSystemContextResource(path);
}
<2021-02-22 10:41>
处直接创建FileSystemContextResource类,该类是一个私有内部类,源码如下:
org.springframework.core.io.FileSystemResourceLoader.FileSystemContextResource
private static class FileSystemContextResource extends FileSystemResource implements ContextResource {
// <2021-02-22 10:41> 构造函数
public FileSystemContextResource(String path) {
super(path);
}
@Override
public String getPathWithinContext() {
return getPath();
}
}
在构造函数中super(path);
直接调用如下代码完成创建:
org.springframework.core.io.FileSystemResource#FileSystemResource(java.lang.String)
public FileSystemResource(String path) {
Assert.notNull(path, "Path must not be null");
// <2021-02-22 10:56> 处理路径为标准格式
this.path = StringUtils.cleanPath(path);
// 创建文件对象
this.file = new File(path);
// 记录文件路径
this.filePath = this.file.toPath();
}
<2021-02-22 10:56>
处可以参考这里。
4:ClassRelativeResourceLoader
该类是基于class的相对路径来加载资源,先来看个例子,在resources
目录下创建test.xml
,然后编写如下测试代码:
@Test
public void testClassRelativeResourceLoader() throws Exception {
ResourceLoader resourceLoader = new ClassRelativeResourceLoader(MyFirstSpringBean.class);
Resource resource = resourceLoader.getResource("../../test.xml");
File file = resource.getFile();
System.out.println(file.getPath());
}
编译后MyFirstSpringBean.class
和test.xml
路径关系如下图:
执行测试代码:
E:\workspace-idea\java-life\target\classes\test.xml
Process finished with exit code 0
来看下是否为实际的系统路径:
来看下源码执行过程,首先执行到代码:
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
...snip...
else {
try {
...snip...
}
catch (MalformedURLException ex) {
// <2021-02-22 11:42>
return getResourceByPath(location);
}
}
}
<2021-02-22 11:42>
处执行代码org.springframework.core.io.ClassRelativeResourceLoader#getResourceByPath
,源码如下:
org.springframework.core.io.ClassRelativeResourceLoader#getResourceByPath
@Override
protected Resource getResourceByPath(String path) {
return new ClassRelativeContextResource(path, this.clazz);
}
继续:
org.springframework.core.io.ClassRelativeResourceLoader.ClassRelativeContextResource#ClassRelativeContextResource
public ClassRelativeContextResource(String path, Class<?> clazz) {
// <2021-02-22 11:43>
super(path, clazz);
this.clazz = clazz;
}
<2021-02-22 11:43>
处执行如下代码:
org.springframework.core.io.ClassPathResource#ClassPathResource(java.lang.String, java.lang.Class<?>)
public ClassPathResource(String path, @Nullable Class<?> clazz) {
Assert.notNull(path, "Path must not be null");
// 该处处理前路径为../../test.xml,处理后路径依然是../../test.xml
this.path = StringUtils.cleanPath(path);
// 记录clazz到属性中
this.clazz = clazz;
}
最后我们来看下测试代码File file = resource.getFile();
是如何通过class和相对路径来获取文件对象的,执行到代码:
org.springframework.core.io.AbstractFileResolvingResource#getFile()
@Override
public File getFile() throws IOException {
// <2021-02-22 11:59>
URL url = getURL();
if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
return VfsResourceDelegate.getResource(url).getFile();
}
return ResourceUtils.getFile(url, getDescription());
}
直接看<2021-02-22 11:59>
处源码:
org.springframework.core.io.ClassPathResource#getURL
@Override
public URL getURL() throws IOException {
// <2021-02-22 12:00>
URL url = resolveURL();
if (url == null) {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
}
return url;
}
继续看<2021-02-22 12:00>
处源码:
@Nullable
protected URL resolveURL() {
// 会进入if
if (this.clazz != null) {
// 直接通过java.lang.Class#getResource获取URL
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
这里就通过class对象获取了对应文件的URL了,后续也都是基于该URL来操作了。
5:ResourcePatternResolver
ResourceLoader接口定义的API只能通过一个位置获取资源对象,如果是我们有根据多个路径获取多个资源的需求,除了for循环一个一个的获取,别无他法,为了解决这个问题,spring定义了接口org.springframework.core.io.support.ResourcePatternResolver
,源码如下:
public interface ResourcePatternResolver extends ResourceLoader {
// 预定义的基于classpath的多个路径协议
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
// 根据模式路径获取多个资源的方法
Resource[] getResources(String locationPattern) throws IOException;
}
最常用的一个子类是PathMatchingResourcePatternResolver
,我们先来看下该类的主要的用法。
5.1:PathMatchingResourcePatternResolver用法
5.1.1:加载单个文件
- 代码
@Test
public void testPathMatchingResourcePatternResolverWithClass() throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("yudaosourcecode/spring/DiUser.class");
System.out.println("resources.length is: " + resources.length);
for (Resource resource : resources) {
System.out.println(resource.getClass());
System.out.println(resource.getFile().getPath());
}
}
- 测试
resources.length is: 1
class org.springframework.core.io.DefaultResourceLoader$ClassPathContextResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiUser.class
5.1.2:通过classpath*加载多个文件
- 代码
@Test
public void testPathMatchingResourcePatternResolverWithClasspathStart() throws IOException {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath*:yudaosourcecode/spring/*.class");
System.out.println("resources.length is: " + resources.length);
for (Resource resource : resources) {
System.out.println(resource.getClass());
System.out.println(resource.getFile().getPath());
}
}
- 测试
resources.length is: 9
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\ConstructorDi.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiPerson.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DiUser.class
class org.springframework.core.io.FileSystemResource
E:\workspace-idea\java-life\target\classes\yudaosourcecode\spring\DongshiDaddyProtocolResolver.class
class org.springframework.core.io.FileSystemResource
...snip...
5.2:PathMatchingResourcePatternResolver源码
该类的路径是org.springframework.core.io.support.PathMatchingResourcePatternResolver
,该类除了支持ResourcePatternResolver新增的classpath*:
外,还支持Ant风格的路径匹配模式,如**/*/*.xml
。来看下源码,首先到方法org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources
,源码如下:
org.springframework.core.io.support.PathMatchingResourcePatternResolver#getResources
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
// 以CLASSPATH_ALL_URL_PREFIX,即org.springframework.core.io.support.ResourcePatternResolver#CLASSPATH_ALL_URL_PREFIX
// String CLASSPATH_ALL_URL_PREFIX = "classpath*:";开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// <2021-02-22 15:50>
// 通过Ant路径匹配器AntPathMatcher判断路径是否为Ant风格路径
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// <2021-02-22 15:53>
return findPathMatchingResources(locationPattern);
}
// 如果是路径不是Ant风格的
else {
// <2021-02-22 15:54>
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
// 如果不是以classpath*开头的
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
<2021-02-22 15:50>
处代码,方法getPathMatcher()
源码如下:
public PathMatcher getPathMatcher() {
// private PathMatcher pathMatcher = new AntPathMatcher();
// 返回接口PathMatcher的实现类AntPathMatcher
return this.pathMatcher;
}
接着调用方法org.springframework.util.AntPathMatcher#isPattern
源码如下:
@Override
public boolean isPattern(String path) {
boolean uriVar = false;
for (int i = 0; i < path.length(); i++) {
char c = path.charAt(i);
if (c == '*' || c == '?') {
return true;
}
if (c == '{') {
uriVar = true;
continue;
}
if (c == '}' && uriVar) {
return true;
}
}
return false;
}
主要是判断是否包含*
,?
,{}
等通配符,有的话就返回true,否则返回false。<2021-02-22 15:53>
处代码源码如下:
org.springframework.core.io.support.PathMatchingResourcePatternResolver#findPathMatchingResources
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
// <2021-02-22 15:54> 获取路径去除通配符的部分,作为根路径
String rootDirPath = determineRootDir(locationPattern);
// 获取通配符部分路径,如classpath*:yudaosourcecode/spring/*.class
// subPattern结果就是*.class
String subPattern = locationPattern.substring(rootDirPath.length());
// 再重复调用getResources获取所有资源的Resouces数组
// 这里一般就是一个文件夹对应的资源,所以长度一般为1
Resource[] rootDirResources = getResources(rootDirPath);
// 最终我们需要的资源的结果集合
Set<Resource> result = new LinkedHashSet<>(16);
// 循环所有的的资源,只留下我们需要的资源
for (Resource rootDirResource : rootDirResources) {
// 该方法直接返回自己,因此该行代码目前没有实际作用
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
// 这里处理bundle,不是和了解,先忽略,有需要再看
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
// 处理vfg,不是很了解,先忽略
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
// 处理jar:,war:,等协议路径
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
// 处理其他路径协议,这里包括classpath*:
else {
// 该处代码根据资源和需要的文件通配符,从文件夹资源下获取对应的资源,如果路径为classpath*:yudaosourcecode/spring/*.class,则rootDirResource为
// URL [file:/E:/workspace-idea/java-life/target/classes/yudaosourcecode/spring/]
// subPattern为*.class
// 比较复杂,先不深究
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}