在《深入Spring IOC源码之Resource》中已经详细介绍了Spring中Resource的抽象,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实例,该实例可以是ClasPathResource、FileSystemResource、UrlResource等,但是该方法返回的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提供了DefaultResourceLoader、FileSystemResourceLoader和ServletContextResourceLoader等单独实现,对ResourcePatternResolver接口则提供了PathMatchingResourcePatternResolver实现。并且ApplicationContext接口继承了ResourcePatternResolver,在实现中,ApplicationContext的实现类会将逻辑代理给相关的单独实现类,如PathMatchingResourceLoader等。在ApplicationContext中ResourceLoaderAware接口,可以将ResourceLoader(自身)注入到实现该接口的Bean中,在Bean中可以将其强制转换成ResourcePatternResolver接口使用(为了安全,强转前需要判断)。在Spring中对ResourceLoader相关类的类图如下:

DefaultResourceLoader类
DefaultResourceLoader是ResourceLoader的默认实现,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