再看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。