Spring源码之ResourceLoader(一):实现类DefaultResourceLoader实现getResource

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。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值