Spring源码之ResourceLoader一:实现类DefaultResourceLoader实现getResource
我们都知道,ApplicationContext做为Spring的容器,提供了资源加载的功能,即能加载单个文件getResource,又能加载符合条件的多个文件getResources,那么其资源加载的能力是怎么实现的呢?首先,我们来看下其实现类AbstractApplicationContext可以分析到getResource由其父类DefaultResourceLoader实现
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
...省略部分代码
}
public class DefaultResourceLoader implements ResourceLoader {
...省略部分代码
@Override
public Resource getResource(String location) { //ResourceLoader中getResource的实现
...省略部分代码
}
}
再看getResources,调用了this.resourcePatternResolver.getResources(locationPattern)
@Override
public Resource[] getResources(String locationPattern) throws IOException {//委托给了resourcePatternResolver方法
return this.resourcePatternResolver.getResources(locationPattern); //this.resourcePatternResolver在构造函数时就完成了创建
}
继续,AbstractApplicationContext在构造时调用了方法getResourcePatternResolver()
public AbstractApplicationContext() { //在构造函数时完成resourcePatternResolver的创建,就时调用getResourcePatternResolver方法
this.resourcePatternResolver = getResourcePatternResolver();
}
继续,进入getResourcePatternResolver方法最终可以看到AbstractApplicationContext的getResources其实委托给了PathMatchingResourcePatternResolver去实现
protected ResourcePatternResolver getResourcePatternResolver() { //这个方法返回了一个PathMatchingResourcePatternResolver
//也就是AbstractApplicationContext委派给了PathMatchingResourcePatternResolver来完成getResources
//PathMatchingResourcePatternResolver构造函数参数为ResourceLoader。这里的this是因为AbstractApplicationContext实现了ResourceLoader
return new PathMatchingResourcePatternResolver(this);
}
PathMatchingResourcePatternResolver构造函数参数为ResourceLoader。这里的this是因为AbstractApplicationContext实现了ResourceLoader。
总结ApplicationContext的两个能力实现:
单文件加载:getResource由ResourceLoader接口的实现类DefaultResourceLoader来实现。
多文件加载:getResources由AbstractApplicationContext委托给了PathMatchingResourcePatternResolver来实现
本文主要讲DefaultResourceLoader的getResource。
话不多说上源码:
public class DefaultResourceLoader implements ResourceLoader {
...省略部分源码
@Override
public Resource getResource(String location) { //ResourceLoader中getResource的实现
Assert.notNull(location, "Location must not be null");
//遍历协议解析,如果有protocolResolver,就通过这个我们自定义的协议解析方式进行解析,ProtocolResolver在Spring中是没有实现的,是留给我们自己可以扩展的自定义的扩展口
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) { //如果url是以"/"开头
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {//CLASSPATH_URL_PREFIX就是classpath:,就是我们常用的xml配置方式中的路径写法,
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());//这种情况就直接通过ClassPathResource进行解析,装载
}
else {//如果不是以"/"开头,也不是以"classpath:"开头,就将其转换为一个URL,然后你去判断它是否是一个文件系统isFileURL(url),然后对应处理
try {
// Try to parse the location as a URL...
URL url = new URL(location);//将location转为URL
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));//FileUrlResource本质上也是一个UrlResource,FileUrlResource增加了一些方法
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);//抛出异常后仍然尝试去通过getResourceByPath解析,和上面"/"开头的一样
}
}
}
}
下面来分析getResource方法:
传参location为传入的url字符串,首先遍历协议解析,如果存在ProtocolResolver,就按照ProtocolResolver的规则来解析url,这里的ProtocolResolver是Spring预留给开发者的扩展口,spring自己是没有任何实现的,开发者可以自己定义解析的规则或者说协议,然后Spring就会按照你定义的规则进行解析url,而不去使用自己的解析协议。
//遍历协议解析,如果有protocolResolver,就通过这个我们自定义的协议解析方式进行解析,ProtocolResolver在Spring中是没有实现的,是留给我们自己可以扩展的自定义的扩展口
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
继续,如果是以“/”开头,说明没有“classpath:”,说明是一个类路径的资源,就调用getResourceByPath(location);
if (location.startsWith("/")) { //如果url是以"/"开头
return getResourceByPath(location);
}
继续,getResourceByPath方法调用了内部类ClassPathContextResource
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
继续,内部类ClassPathContextResource继承了ClassPathResource,比ClassPathResource多了一个可以创建相对资源的url的能力
// 本身仍然是一个ClassPathResource(基于类路径的资源),只不过比它的父类ClassPathResource多了一个createRelative方法
protected static class ClassPathContextResource extends ClassPathResource implements ContextResource {
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
@Override
public String getPathWithinContext() {
return getPath();
}
@Override
public Resource createRelative(String relativePath) { //可以创建一个相对资源的url,relativePath是一个相对路径
String pathToUse = StringUtils.applyRelativePath(getPath(), relativePath);
return new ClassPathContextResource(pathToUse, getClassLoader());
}
}
}
继续,ClassPathContextResource的构造方法委托给了它的父类,
public ClassPathContextResource(String path, @Nullable ClassLoader classLoader) {
super(path, classLoader);
}
继续,进入ClassPathContextResource父类ClassPathResource的构造方法:
public ClassPathResource(String path, @Nullable ClassLoader classLoader) { //如果有传入ClassLoader就用传入的,如果没有就 ClassUtils.getDefaultClassLoader(),创建一个线程的ClassLoader
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
其中的ClassLoader如果🈶️传入,就用传入的ClassLoader,没有就用ClassUtils.getDefaultClassLoader(),创建一个线程的ClassLoader,
最终ClassPathResource返回的也是一个Resource。
这是url以“/”开头的情况。下面,如果以“classpath:”开头,其实就是将“classpath:”截取掉,然后还是和以“/”开头的一样,交给ClassPathResource处理:
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {//CLASSPATH_URL_PREFIX就是classpath:,就是我们常用的xml配置方式中的路径写法,
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());//这种情况就直接通过ClassPathResource进行解析,装载
}
继续,如果url不是以"/"开头,也不是以"classpath:"开头,就将其转换为一个URL,然后你去判断它是否是一个文件系统isFileURL(url),然后用UrlResource解析资源路径。
else {//如果不是以"/"开头,也不是以"classpath:"开头,就将其转换为一个URL,然后你去判断它是否是一个文件系统isFileURL(url),然后对应处理
try {
// Try to parse the location as a URL...
URL url = new URL(location);//将location转为URL
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));//FileUrlResource本质上也是一个UrlResource,FileUrlResource增加了一些方法
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);//抛出异常后仍然尝试去通过getResourceByPath解析,和上面"/"开头的一样
}
}
其中new FileUrlResource(url)中FileUrlResource其实也是一个UrlResource,只是比UrlResource多了一些能力,比如:
@Override
public OutputStream getOutputStream() throws IOException {
return Files.newOutputStream(getFile().toPath());
}
@Override
public WritableByteChannel writableChannel() throws IOException {
return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
}
@Override
public Resource createRelative(String relativePath) throws MalformedURLException {
return new FileUrlResource(createRelativeURL(relativePath));
}
但是其实FileUrlResource解析这个url还是通过其父类来解析,只不过比起父类多提供了一些能力,其构造方法:
public FileUrlResource(URL url) {
super(url);
}
最后,如果用URL不能解析的话,Spring还是会尝试用getResourceByPath解析,当然会抛出异常后解析
try {
// Try to parse the location as a URL...
URL url = new URL(location);//将location转为URL
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));//FileUrlResource本质上也是一个UrlResource,FileUrlResource增加了一些方法
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);//抛出异常后仍然尝试去通过getResourceByPath解析,和上面"/"开头的一样
}
总结,上面就是加载文件用到的getResource,比如我们通过xml配置资源文件properties经常用到的"classpath:application.properties"。
下一篇文章讲加载多资源文件getResources。