深入Spring IOC源码之ResourceLoader
在《深入Spring IOC源码之Resource》中已经详细介绍了Spring中Resource的抽象,Resource接口有很多实现类,我们当然可以使用各自的构造函数创建符合需求的Resource实例,然而Spring提供了ResourceLoader接口用于实现不同的Resource加载策略,即将不同Resource实例的创建交给ResourceLoader来计算。
public inte易做图ce 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 inte易做图ce 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 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方法的实现逻辑和DefaultResourceLoader相同,不同的是它实现了自己的getResourceByPath方法,即当UrlResource创建失败时,它会使用FileSystemContextResource实例而不是ClassPathContextResource:
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemContextResource(path);
}
使用该类时要特别注意的一点:即使location以”/”开头,资源的查找还是相对于VM启动时的相对路径而不是绝对路径(从以上代码片段也可以看出,它会先截去开头的”/”),这个和Servlet Container保持一致。如果需要使用绝对路径,需要添加”file:”前缀。
ServletContextResourceLoader类
ServletContextResourceLoader类继承自DefaultResourceLoader,和FileSystemResourceLoader一样,它的getResource方法的实现逻辑和DefaultResourceLoader相同,不同的是它实现了自己的getResourceByPath方法,即当UrlResource创建失败时,它会使用ServletContextResource实例:
protected Resource getResourceByPath(String path) {
return new ServletContextResource(this.servletContext, path);
}
这里的path即使以”/”开头,也是相对ServletContext的路径,而不是绝对路径,要使用绝对路径,需要添加”file:”前缀。
PathMatchingResourcePatternResolver类
PathMatchingResourcePatternResolver类实现了ResourcePatternResolver接口,它包含了对ResourceLoader接口的引用,在对继承自ResourceLoader接口的方法的实现会代理给该引用,同时在getResources()方法实现中,当找到一个匹配的资源location时,可以使用该引用解析成Resource实例。默认使用DefaultResourceLoader类,用户可以使用构造函数传入自定义的ResourceLoader。
PathMatchingResourcePatternResolver还包含了一个对PathMatcher接口的引用,该接口基于路径字符串实现匹配处理,如判断一个路径字符串是否包含通配符(’*’、’?’),判断给定的path是否匹配给定的pattern等。Spring提供了AntPathMatcher对PathMatcher的默认实现,表达该PathMatcher是采用Ant风格的实现。其中PathMatcher的接口定义如下:
public inte易做图ce PathMatcher {
boolean isPattern(String path);
boolean match(String pattern, String path);
boolean matchStart(String pattern, String path);
String extractPathWithinPattern(String pattern, String path);
}
isPattern(S
补充:软件开发 , Java ,