参考文章:

http://www.iocoder.cn/

 

资源描述:Resource 

统一的接口

public interface Resource extends InputStreamSource {

	/**
	 * 资源是否存在
	 */
	boolean exists();

	/**
	 * 资源是否可读
	 */
	default boolean isReadable() {
		return exists();
	}

	/**
	 * 是否打开
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * 是否是文件
	 */
	default boolean isFile() {
		return false;
	}

	/**
	 * 返回资源的URL地址
	 */
	URL getURL() throws IOException;

	/**
	 * 返回资源的URI地址
	 */
	URI getURI() throws IOException;

	/**
	 * 获得资源的文件标识
	 */
	File getFile() throws IOException;

	/**
	 * byte读取通道
	 */
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	/**
	 * 资源长度
	 */
	long contentLength() throws IOException;

	/**
	 * 最后修改时间
	 */
	long lastModified() throws IOException;

	/**
	 * 根据资源的相对路径创建新资源
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * 资源名称
	 */
	@Nullable
	String getFilename();

	/**
	 * 资源描述
	 */
	String getDescription();

}

类图

spring学习(二)——资源获取

抽象类

org.springframework.core.io.AbstractResource ,为 Resource 接口的默认抽象实现。它实现了 Resource 接口的大部分的公共实现,自定义资源的时候,最好是继承AbstractResource而不是实现Resource

实现类

  • FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。
  • ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
  • UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
  • InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。

 

资源加载:ResourceLoader 

统一接口

public interface ResourceLoader {

	/** Pseudo URL prefix for loading from the class path: "classpath:". */
	// CLASSPATH URL 前缀。默认为:"classpath:"
	String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

	//根据所提供资源的路径 location 返回 Resource 实例
	Resource getResource(String location);
	@Nullable
	ClassLoader getClassLoader();

}

类图

spring学习(二)——资源获取

子类

  • DefaultResourceLoader:org.springframework.core.io.DefaultResourceLoader 是 ResourceLoader 的默认实现
  • FileSystemResourceLoader:它继承 DefaultResourceLoader ,且覆写了 #getResourceByPath(String) 方法,使之从文件系统加载资源并以 FileSystemResource 类型返回。
  • ClassRelativeResourceLoader: DefaultResourceLoader 的另一个子类的实现。和 FileSystemResourceLoader 类似,在实现代码的结构上类似,也是覆写 #getResourceByPath(String path) 方法,并返回其对应的 ClassRelativeContextResource 的资源类型
  • ResourcePatternResolver:ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例
  • PathMatchingResourcePatternResolver:除了支持 ResourceLoader 和 ResourcePatternResolver 新增的 "classpath*:" 前缀外,还支持 Ant 风格的路径匹配模式

实现类

DefaultResourceLoader

org.springframework.core.io.DefaultResourceLoader 是 ResourceLoader 的默认实现

构造函数

	/**
	 * 使用的 ClassLoader 为默认的 ClassLoader(一般 Thread.currentThread()#getContextClassLoader() )
	 */
	public DefaultResourceLoader() {
		this.classLoader = ClassUtils.getDefaultClassLoader();
	}

	/**
	 * 在使用带参数的构造函数时,可以通过 ClassUtils#getDefaultClassLoader()获取。
	 */
	public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
		this.classLoader = classLoader;
	}

getResource 方法

ResourceLoader 中最核心的方法为 #getResource(String location) ,它根据提供的 location 返回相应的 Resource 。而 DefaultResourceLoader 对该方法提供了核心实现

	@Override
	public Resource getResource(String location) {
		Assert.notNull(location, "Location must not be null");

		// 首先,通过 ProtocolResolver 来加载资源
		for (ProtocolResolver protocolResolver : this.protocolResolvers) {
			Resource resource = protocolResolver.resolve(location, this);
			if (resource != null) {
				return resource;
			}
		}
		// 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
		if (location.startsWith("/")) {
			return getResourceByPath(location);
		}
		// 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
		else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
		}
		// 然后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
		else {
			try {
				// Try to parse the location as a URL...
				URL url = new URL(location);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			}
			catch (MalformedURLException ex) {
				// 最后,返回 ClassPathContextResource 类型的资源
				// No URL -> resolve as resource path.
				// location.startsWith("/") 其实又回到最初的判断了
				return getResourceByPath(location);
			}
		}
	}

ProtocolResolver

用户自定义协议资源解决策略,作为 DefaultResourceLoader 的 SPI:它允许用户自定义资源加载协议,而不需要继承 ResourceLoader 的子类

ProtocolResolver 接口,仅有一个方法 Resource resolve(String location, ResourceLoader resourceLoader)

@Nullable
Resource resolve(String location, ResourceLoader resourceLoader);

 

 

FileSystemResourceLoader

它继承 DefaultResourceLoader ,且覆写了 #getResourceByPath(String) 方法,使之从文件系统加载资源并以 FileSystemResource 类型返回,这样我们就可以得到想要的资源类型

@Override
	protected Resource getResourceByPath(String path) {
		//截取第一个/分界线
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		//// 创建 FileSystemContextResource 类型的资源
		return new FileSystemContextResource(path);
	}

FileSystemContextResource

/**
	 * 实现 ContextResource 接口,并实现对应的 #getPathWithinContext() 接口方法
	 */
	private static class FileSystemContextResource extends FileSystemResource implements ContextResource {

		public FileSystemContextResource(String path) {
			super(path);
		}

		@Override
		public String getPathWithinContext() {
			return getPath();
		}
	}

 

ClassRelativeResourceLoader

 DefaultResourceLoader 的另一个子类的实现。和 FileSystemResourceLoader 类似,在实现代码的结构上类似,也是覆写 #getResourceByPath(String path) 方法,并返回其对应的 ClassRelativeContextResource 的资源类型

ClassRelativeResourceLoader 扩展的功能是,可以根据给定的class 所在包或者所在包的子包下加载资源。

ResourcePatternResolver

ResourceLoader 的扩展,它支持根据指定的资源路径匹配模式每次返回多个 Resource 实例

public interface ResourcePatternResolver extends ResourceLoader {

	/**
	 * 新的路径前缀
	 */
	String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

	/**
	 * ResourcePatternResolver 在 ResourceLoader 的基础上增加了 #getResources(String locationPattern) 方法,
	 * 以支持根据路径匹配模式返回多个 Resource 实例
	 * @param locationPattern
	 * @return
	 * @throws IOException
	 */
	Resource[] getResources(String locationPattern) throws IOException;

}

PathMatchingResourcePatternResolver

除了支持 ResourceLoader 和 ResourcePatternResolver 新增的 "classpath*:" 前缀外,还支持 Ant 风格的路径匹配模式

 

构造函数

/**
	 * 可以指定一个 ResourceLoader,如果不指定的话,它会在内部构造一个 DefaultResourceLoader
	 */
	private final ResourceLoader resourceLoader;

	/**
	 * Ant 路径匹配器
	 */
	private PathMatcher pathMatcher = new AntPathMatcher();


	/**
	 * 使用默认资源加载
	 */
	public PathMatchingResourcePatternResolver() {
		this.resourceLoader = new DefaultResourceLoader();
	}

	/**
	 * 资源加载器
	 */
	public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
		Assert.notNull(resourceLoader, "ResourceLoader must not be null");
		this.resourceLoader = resourceLoader;
	}

	/**
	 * 类加载器
	 */
	public PathMatchingResourcePatternResolver(@Nullable ClassLoader classLoader) {
		this.resourceLoader = new DefaultResourceLoader(classLoader);
	}

getResource 方法

@Override
	public Resource getResource(String location) {
		return getResourceLoader().getResource(location);
	}

getResources 方法

	@Override
	public Resource[] getResources(String locationPattern) throws IOException {
		Assert.notNull(locationPattern, "Location pattern must not be null");
		//以classpath*: 开头
		if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
			// 路径包含通配符
			// a class path resource (multiple resources for same name possible)
			if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
				// a class path resource pattern
				return findPathMatchingResources(locationPattern);
			}
			// 路径不包含通配符
			else {
				// all class path resources with the given name
				return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
			}
		}
		// 不以 "classpath*:" 开头
		else {
			// 通常只在这里的前缀后面查找模式
			// Generally only look for a pattern after a prefix here,
			// Tomcat 上只有在 “*/ ”分隔符之后才为其 “war:” 协议
			// and on Tomcat only after the "*/" separator for its "war:" protocol.
			int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
					locationPattern.indexOf(':') + 1);
			// 路径包含通配符
			if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
				// a file pattern
				return findPathMatchingResources(locationPattern);
			}
			// 路径不包含通配符
			else {
				// a single resource with the given name
				return new Resource[] {getResourceLoader().getResource(locationPattern)};
			}
		}
	}

findAllClassPathResources

地址不含通配符的时候

/**
	 * 该方法返回 classes 路径下和所有 jar 包中的所有相匹配的资源。
	 * "classpath*:" 开头但是不包含通配符
	 */
	protected Resource[] findAllClassPathResources(String location) throws IOException {
		String path = location;
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		//真正执行加载的地方
		Set<Resource> result = doFindAllClassPathResources(path);
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved classpath location [" + location + "] to resources " + result);
		}
		return result.toArray(new Resource[0]);
	}

	/**
	 * Find all class location resources with the given path via the ClassLoader.
	 * Called by {@link #findAllClassPathResources(String)}.
	 * @param path the absolute path within the classpath (never a leading slash)
	 * @return a mutable Set of matching Resource instances
	 * @since 4.1.1
	 */
	protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
		Set<Resource> result = new LinkedHashSet<>(16);
		//根据 ClassLoader 加载路径下的所有资源
		ClassLoader cl = getClassLoader();
		//如果当前父类加载器不为 null ,则通过父类向上迭代获取资源,否则调用 #getBootstrapResources()
		Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
		while (resourceUrls.hasMoreElements()) {
			URL url = resourceUrls.nextElement();
			// 将 URL 转换成 UrlResource
			result.add(convertClassLoaderURL(url));
		}
		//加载所有jar包 传入的为/的时候
		if ("".equals(path)) {
			addAllClassLoaderJarRoots(cl, result);
		}
		return result;
	}

findPathMatchingResources

地址包含通配符的时候

	/**
	 * 确定目录,获取该目录下得所有资源。
	 * 在所获得的所有资源后,进行迭代匹配获取我们想要的资源。
	 */
	protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
		// 确定根路径、子路径
		String rootDirPath = determineRootDir(locationPattern);
		String subPattern = locationPattern.substring(rootDirPath.length());
		//获取根路径下的资源
		Resource[] rootDirResources = getResources(rootDirPath);
		Set<Resource> result = new LinkedHashSet<>(16);
		//遍历
		for (Resource rootDirResource : rootDirResources) {
			rootDirResource = resolveRootDirResource(rootDirResource);
			URL rootDirUrl = rootDirResource.getURL();
			//bundle 资源类型
			if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
				URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
				if (resolvedUrl != null) {
					rootDirUrl = resolvedUrl;
				}
				rootDirResource = new UrlResource(rootDirUrl);
			}
			// vfs 资源类型
			if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
			}
			//jar资源类型
			else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
				result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
			}
			//其他资源类型
			else {
				result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
		}
		// 转换成 Resource 数组返回
		return result.toArray(new Resource[0]);
	}

 

determineRootDir

主要是用于确定根路径

protected String determineRootDir(String location) {
		// 找到冒号的后一位
		int prefixEnd = location.indexOf(':') + 1;
		//根目录结束位置
		int rootDirEnd = location.length();
		// 在从冒号开始到最后的字符串中,循环判断是否包含通配符,如果包含,则截断最后一个由”/”分割的部分。
		// 例如:在我们路径中,就是最后的ap?-context.xml这一段。再循环判断剩下的部分,直到剩下的路径中都不包含通配符。
		while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
			rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
		}
		// 如果查找完成后,rootDirEnd = 0 了,则将之前赋值的 prefixEnd 的值赋给 rootDirEnd ,也就是冒号的后一位
		if (rootDirEnd == 0) {
			rootDirEnd = prefixEnd;
		}
		//截图更目录
		return location.substring(0, rootDirEnd);
	}

相关文章: