Spring 5 Resource 源码注释

58 篇文章 1 订阅
56 篇文章 3 订阅

UML类图

UML类图

Resource

/**
 * Interface for a resource descriptor that abstracts from the actual
 * type of underlying resource, such as a file or class path resource.
 * <p>
 *     资源描述符的接口,该描述符从底层资源的实际类型中抽象出来,例如一个
 *     文件或类路径资源
 * </p>
 * <p>An InputStream can be opened for every resource if it exists in
 * physical form, but a URL or File handle can just be returned for
 * certain resources. The actual behavior is implementation-specific.
 * <p>
 *     如果一个输入流以物理形式存在,则认为每个资源可以被打开,但是仅可以
 *     为某些资源返回一个网址或一个文件句柄。实际行为是特定实现的。
 * </p>
 * <p>
 *     参考博客:
 *     <ul style='margin-top:0px'>
 *         <li>https://blog.csdn.net/readiay/article/details/52862379</li>
 *     </ul>
 * </p>
 * @author Juergen Hoeller
 * @since 28.12.2003
 * @see #getInputStream()
 * @see #getURL()
 * @see #getURI()
 * @see #getFile()
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see FileUrlResource
 * @see FileSystemResource
 * @see ClassPathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */
public interface Resource extends InputStreamSource {

	/**
	 * Determine whether this resource actually exists in physical form.
	 * <p>确定此资源是否实际以物理形式存在</p>
	 * <p>This method performs a definitive existence check, whereas the
	 * existence of a {@code Resource} handle only guarantees a valid
	 * descriptor handle.
	 * <p>这个方法执行一个确定存在性的检查,而{@code Resource}句柄的存在只能
	 * 保证有效的描述符句柄</p>
	 */
	boolean exists();

	/**
	 * Indicate whether non-empty contents of this resource can be read via
	 * {@link #getInputStream()}.
	 * <p>
	 *  表明是否可以通过{@link #getInputStream()}读取此资源的非空内容
	 * </p>
	 * <p>Will be {@code true} for typical resource descriptors that exist
	 * since it strictly implies {@link #exists()} semantics as of 5.1.
	 * Note that actual content reading may still fail when attempted.
	 * However, a value of {@code false} is a definitive indication
	 * that the resource content cannot be read.
	 * <p>
	 *     对于存在典型资源描述符,将会为{@code true},因为从5.1开始,它严格
	 *     暗示{@link #exists()}语义。注意当尝试读取实际内容可能仍然失败。然而,
	 *     值为{@code false}明确表示无法读取资源内容。
	 * </p>
	 * @see #getInputStream()
	 * @see #exists()
	 */
	default boolean isReadable() {
		return exists();
	}

	/**
	 * Indicate whether this resource represents a handle with an open stream.
	 * If {@code true}, the InputStream cannot be read multiple times,
	 * and must be read and closed to avoid resource leaks.
	 * <p>
	 *     表明这个资源是否代表具有开放流的句柄。如果为{@code true},这个流不能被多次
	 *     读取,并且必须将其读取并关闭以避免资源泄露
	 * </p>
	 * <p>Will be {@code false} for typical resource descriptors.
	 * <p>
	 *     会为{@code false}对于典型资源描述符
	 * </p>
	 */
	default boolean isOpen() {
		return false;
	}

	/**
	 * Determine whether this resource represents a file in a file system.
	 * A value of {@code true} strongly suggests (but does not guarantee)
	 * that a {@link #getFile()} call will succeed.
	 * <p>确定这个资源是否代表在文件系统中的一个文件。调用{@link #getFile()}成功强烈
	 * 建议值为{@code true}</p>
	 * <p>This is conservatively {@code false} by default.
	 * <p>默认情况下,这是保守为{@code false}</p>
	 * @since 5.0
	 * @see #getFile()
	 */
	default boolean isFile() {
		return false;
	}

	/**
	 * Return a URL handle for this resource.
	 * <p>返回此资源的URL句柄</p>
	 * @throws IOException if the resource cannot be resolved as URL,
	 * i.e. if the resource is not available as descriptor
	 * 		-- 如果资源不能被解析为网址,例如,如果资源不可用作描述符
	 */
	URL getURL() throws IOException;

	/**
	 * Return a URI handle for this resource.
	 * <p>为这个资源返回一个网址句柄</p>
	 * @throws IOException if the resource cannot be resolved as URI,
	 * i.e. if the resource is not available as descriptor
	 * 		-- 如果资源不能被解析为网址,例如,如果资源不可用作描述符
	 * @since 2.5
	 */
	URI getURI() throws IOException;

	/**
	 * Return a File handle for this resource.
	 * <p>为这个资源返回一个文件句柄</p>
	 * @throws java.io.FileNotFoundException if the resource cannot be resolved as
	 * absolute file path, i.e. if the resource is not available in a file system
	 * 		-- 如果资源不能被解析为绝对文件路径,例如,如果资源在文件系统中不可用
	 * @throws IOException in case of general resolution/reading failures
	 * 					-- 一般情况下,解析或读取失败
	 * @see #getInputStream()
	 */
	File getFile() throws IOException;

	/**
	 * Return a {@link ReadableByteChannel}.
	 * <p>返回一个{@link ReadableByteChannel}</p>
	 * <p>It is expected that each call creates a <i>fresh</i> channel.
	 * <p>预计每次调用都会创建一个新的通道</p>
	 * <p>The default implementation returns {@link Channels#newChannel(InputStream)}
	 * with the result of {@link #getInputStream()}.
	 * <p>
	 *     这个默认实现返回{@link Channels#newChannel(InputStream)},结果为
	 *     {@link #getInputStream()}
	 * </p>
	 * @return the byte channel for the underlying resource (must not be {@code null})
	 * 			-- 基础资源的字节通道(不可以为{@code null})
	 * @throws java.io.FileNotFoundException if the underlying resource doesn't exist
	 * 					-- 如果底层资源不存在
	 * @throws IOException if the content channel could not be opened
	 * 				-- 如果内容通道不能打开
	 * @since 5.0
	 * @see #getInputStream()
	 */
	default ReadableByteChannel readableChannel() throws IOException {
		//channel是一个对象,这个对象代表一个与硬盘,文件,网络socket,程序组件的打开链接,或者另一个可以去执行写,
		// 读和其他I/O操作的实体。channel可以在字节buffer和基于操作系统的I/O服务源或目的之间高效地传输数据。
		//获取输入流,并开启NIO通道
		return Channels.newChannel(getInputStream());
	}

	/**
	 * Determine the content length for this resource.
	 * <p>确定此资源的内容长度</p>
	 * @throws IOException if the resource cannot be resolved
	 * (in the file system or as some other known physical resource type)
	 * 			-- 如果资源不可以被解析(在文件系统中或作为其他一些已知的物理资源类型)
	 */
	long contentLength() throws IOException;

	/**
	 * Determine the last-modified timestamp for this resource.
	 * <p>确定此资源的最后修改的时间戳</p>
	 * @throws IOException if the resource cannot be resolved
	 * (in the file system or as some other known physical resource type)
	 * 		-- 如果资源不可以被解析(在文件系统中或作为其他一些已知的物理资源类型)
	 */
	long lastModified() throws IOException;

	/**
	 * Create a resource relative to this resource.
	 * <p>创建相对于该资源的资源</p>
	 * @param relativePath the relative path (relative to this resource)
	 *                     	-- 相对路径(相对这个资源)
	 * @return the resource handle for the relative resource
	 * 			-- 相对资源的资源句柄
	 * @throws IOException if the relative resource cannot be determined
	 * 				-- 如果相对资源不能确定
	 */
	Resource createRelative(String relativePath) throws IOException;

	/**
	 * Determine a filename for this resource, i.e. typically the last
	 * part of the path: for example, "myfile.txt".
	 * <p>确定此资源的文件名,即,常用路径的最后一部分:例如'myfile.txt'</p>
	 * <p>Returns {@code null} if this type of resource does not
	 * have a filename.
	 * <p>如果资源类没有文件名,则返回{@code null}</p>
	 */
	@Nullable
	String getFilename();

	/**
	 * Return a description for this resource,
	 * to be used for error output when working with the resource.
	 * <p>返回此资源的一个描述符,在使用资源时用于错误输出</p>
	 * <p>Implementations are also encouraged to return this value
	 * from their {@code toString} method.
	 * <p>翻鼓励实现从其{@code toString}方法中返回此值</p>
	 * @see Object#toString()
	 */
	String getDescription();

}

AbstrctResource

/**
 * Convenience base class for {@link Resource} implementations,
 * pre-implementing typical behavior.
 * <p>{@link Resource}的方便基础类实现,预实现通用特性</p>
 * <p>The "exists" method will check whether a File or InputStream can
 * be opened; "isOpen" will always return false; "getURL" and "getFile"
 * throw an exception; and "toString" will return the description.
 * <p>
 *     'exists'方法会检查一个文件或流是否能打开;'isOpen'会总是返回false;
 *     'getURL'和'getFile'会抛出一个异常;和'toString'会返回资源描述符
 * </p>
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 28.12.2003
 */
public abstract class AbstractResource implements Resource {

	/**
	 * This implementation checks whether a File can be opened,
	 * falling back to whether an InputStream can be opened.
	 * This will cover both directories and content resources.
	 * <p>
	 *     该实现会检查一个文件或流是否能打开,回到是否可以打开InputStream。
	 *     这会涵盖描述符和内容资源
	 * </p>
	 */
	@Override
	public boolean exists() {
		// Try file existence: can we find the file in the file system? 尝试文件存在:我们能在文件系统中找到文件吗?
		//如果资源是文件
		if (isFile()) {
			try {
				//获取资源对应的文件,返回其是否存在
				return getFile().exists();
			}
			catch (IOException ex) {
				//获取日志
				Log logger = LogFactory.getLog(getClass());
				//如果当前日志级别是调试
				if (logger.isDebugEnabled()) {
					//打印日志,并获取描述符加以描述异常信息
					logger.debug("Could not retrieve File for existence check of " + getDescription(), ex);
				}
			}
		}
		// Fall back to stream existence: can we open the stream? 退回到流的存在:我们可以打开流吗?
		try {
			//获取输入流,并关闭
			getInputStream().close();
			//如果获取输入流并关闭成功又没有抛出任何异常,就认为存在,返回true
			return true;
		}
		catch (Throwable ex) {
			//获取日志对象
			Log logger = LogFactory.getLog(getClass());
			//如果当前日志级别是调试
			if (logger.isDebugEnabled()) {
				//打印日志,并获取描述符加以描述异常信息
				logger.debug("Could not retrieve InputStream for existence check of " + getDescription(), ex);
			}
			//如果获取输入流或关闭时抛出了任何异常,都认为其不存在,返回false
			return false;
		}
	}

	/**
	 * This implementation always returns {@code true} for a resource
	 * that {@link #exists() exists} (revised as of 5.1).
	 * <p>该实现总是为存在的资源返回true</p>
	 */
	@Override
	public boolean isReadable() {
		return exists();
	}

	/**
	 * This implementation always returns {@code false}.
	 * <p>该实现总是返回{@code false}</p>
	 */
	@Override
	public boolean isOpen() {
		return false;
	}

	/**
	 * This implementation always returns {@code false}.
	 * 该实现总是返回{@code false}
	 */
	@Override
	public boolean isFile() {
		return false;
	}

	/**
	 * This implementation throws a FileNotFoundException, assuming
	 * that the resource cannot be resolved to a URL.
	 * <p>该实现抛出一个FileNotFoundException,假设该资源无法解析成URL</p>
	 */
	@Override
	public URL getURL() throws IOException {
		//抛出文件没有找到异常,并获取资源描述加以描述异常新
		throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
	}

	/**
	 * This implementation builds a URI based on the URL returned
	 * by {@link #getURL()}.
	 * <p>
	 *     该实现基于{@link #getURL()}返回的URL构建URL
	 * </p>
	 */
	@Override
	public URI getURI() throws IOException {
		//获取URL
		URL url = getURL();
		try {
			// 为url创建一个URI实例,首先用'%20'URI编码替换空格
			return ResourceUtils.toURI(url);
		}
		catch (URISyntaxException ex) {
			//如果出现URL语法异常,就抛出嵌套IO异常,并加以描述是哪个url引起的异常
			throw new NestedIOException("Invalid URI [" + url + "]", ex);
		}
	}

	/**
	 * This implementation throws a FileNotFoundException, assuming
	 * that the resource cannot be resolved to an absolute file path.
	 * <p>该实现会抛出一个FileNotFoundException,加上资源不能解析一个绝对文件路径</p>
	 */
	@Override
	public File getFile() throws IOException {
		//抛出FileNotFoundException异常,并获取资源描述符加以描述异常信息
		throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
	}

	/**
	 * This implementation returns {@link Channels#newChannel(InputStream)}
	 * with the result of {@link #getInputStream()}.
	 * <p>该实现返回{@link Channels#newChannel(InputStream)},结果为{@link #getInputStream()}</p>
	 * <p>This is the same as in {@link Resource}'s corresponding default method
	 * but mirrored here for efficient JVM-level dispatching in a class hierarchy.
	 * <p>这与{@link Resource}的相应默认方法相同,但在此处进行了镜像,以实现类层次结构的有效
	 * JVM级别分派</p>
	 */
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		//channel是一个对象,这个对象代表一个与硬盘,文件,网络socket,程序组件的打开链接,或者另一个可以去执行写,
		// 读和其他I/O操作的实体。channel可以在字节buffer和基于操作系统的I/O服务源或目的之间高效地传输数据。
		//获取输入流,并开启NIO通道
		return Channels.newChannel(getInputStream());
	}

	/**
	 * This implementation reads the entire InputStream to calculate the
	 * content length. Subclasses will almost always be able to provide
	 * a more optimal version of this, e.g. checking a File length.
	 * <p>
	 *     该实现读取整个输入流来计算内容长度。子类几乎总是能够提供更好的版本,例如
	 *     检查文件长度
	 * </p>
	 * @see #getInputStream()
	 */
	@Override
	public long contentLength() throws IOException {
		//获取输入流
		InputStream is = getInputStream();
		try {
			//初始化大小为0
			long size = 0;
			//初始化缓冲区字节数组长度为256
			byte[] buf = new byte[256];
			//定义每次读取到的字节数
			int read;
			//从is中读取字节填充buf,并返回每次已读到的字节数,如果read不等于-1表示还有数据未读完,就继续循环读取,指定得到-1
			while ((read = is.read(buf)) != -1) {
				//将每次读取的字节数进行累计到size
				size += read;
			}
			//返回已读取完的字节数,相当于输入流的总长度
			return size;
		}
		finally {
			try {
				//关闭输入流
				is.close();
			}
			catch (IOException ex) {
				//获取日志对象
				Log logger = LogFactory.getLog(getClass());
				//如果当前设置的日志级别是调试级别
				if (logger.isDebugEnabled()) {
					//打印日志,并获取描述符加以描述异常信息
					logger.debug("Could not close content-length InputStream for " + getDescription(), ex);
				}
			}
		}
	}

	/**
	 * This implementation checks the timestamp of the underlying File,
	 * if available.
	 * <p>该实现检查底层文件的时间戳</p>
	 * @see #getFileForLastModifiedCheck()
	 */
	@Override
	public long lastModified() throws IOException {
		//获取用于时间戳检查的文件
		File fileToCheck = getFileForLastModifiedCheck();
		//获取fielToCheck的最后一次修改时间戳
		long lastModified = fileToCheck.lastModified();
		//如果最后一次修改时间戳为0 且 fileToCheck不存在
		if (lastModified == 0L && !fileToCheck.exists()) {
			//抛出文件没有找到异常,并获取该资源描述符加以描述异常信息
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		//返回最后一次修改时间戳
		return lastModified;
	}

	/**
	 * Determine the File to use for timestamp checking.
	 * <p>确定用于时间戳检查的文件</p>
	 * <p>The default implementation delegates to {@link #getFile()}.
	 * <p>默认实现委托给{@link #getFile()}</p>
	 * @return the File to use for timestamp checking (never {@code null})
	 * 			用于时间戳检查的文件(切勿返回{@code null})
	 * @throws FileNotFoundException if the resource cannot be resolved as
	 * an absolute file path, i.e. is not available in a file system
	 * 			-- 如果资源无法解析为绝对文件路径,即文件系统不可用
	 * @throws IOException in case of general resolution/reading failures
	 * 				一般情况下的解析或读取失败
	 */
	protected File getFileForLastModifiedCheck() throws IOException {
		return getFile();
	}

	/**
	 * This implementation throws a FileNotFoundException, assuming
	 * that relative resources cannot be created for this resource.
	 * <p>
	 *     这个实现会抛出FileNotFoundException异常,假设无法为此资源创建相对路径
	 * </p>
	 */
	@Override
	public Resource createRelative(String relativePath) throws IOException {
		//抛出FileNotFoundException异常,并获取资源描述符加以描述异常信息
		throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
	}

	/**
	 * This implementation always returns {@code null},
	 * assuming that this resource type does not have a filename.
	 * <p>
	 *     这个实现总是返回{@code null},假设此资源类型没有文件名
	 * </p>
	 */
	@Override
	@Nullable
	public String getFilename() {
		return null;
	}


	/**
	 * This implementation compares description strings.
	 * 该实现比较描述符字符串
	 * @see #getDescription()
	 */
	@Override
	public boolean equals(@Nullable Object other) {
		//如果 other是该对象 或者 (other是Resource的子类或本身 且 other强转成Resource后取出描述符与该对象的描述符相等
		return (this == other || (other instanceof Resource &&
				((Resource) other).getDescription().equals(getDescription())));
	}

	/**
	 * This implementation returns the description's hash code.
	 * <p>该实现返回该资源的描述符hashCode</p>
	 * @see #getDescription()
	 */
	@Override
	public int hashCode() {
		return getDescription().hashCode();
	}

	/**
	 * This implementation returns the description of this resource.
	 * <p>该实现返回该资源的描述符</p>
	 * @see #getDescription()
	 */
	@Override
	public String toString() {
		return getDescription();
	}

}

AbstractFileResolvingResource

/**
 * Abstract base class for resources which resolve URLs into File references,
 * such as {@link UrlResource} or {@link ClassPathResource}.
 * <p>用于将URL解析为文件资源的抽象基础类</p>
 * <p>Detects the "file" protocol as well as the JBoss "vfs" protocol in URLs,
 * resolving file system references accordingly.
 * <p>在URLs中检查'file'协议以及JBoos的'vfs'协议,解析相应的文件系统资源</p>
 * @author Juergen Hoeller
 * @since 3.0
 */
public abstract class AbstractFileResolvingResource extends AbstractResource {

	@Override
	public boolean exists() {
		try {
			//获取资源的URL
			URL url = getURL();
			//如果url是否指向文件系统的资源,即具有协议'file','vfsfile','vfs'
			if (ResourceUtils.isFileURL(url)) {
				// Proceed with file system resolution 进行文件系统解析
				//获取资源对应的文件,返回其是否存在
				return getFile().exists();
			}
			else {
				// Try a URL connection content-length header
				//打开url网络连接
				URLConnection con = url.openConnection();
				//定制给定的con,本类实现:如果con的类名是以'JNLP'开头,就意味着这个连接是JNLP,如果是JNLP,就设置连接缓存为true,否则为false;
				//以及,如果con是HttpUrlConnection的子类或本类,会简单设置con的请求方法为'HEAD'
				customizeConnection(con);
				//如果con是HttpURLConnection的子类或本身,会将其强转赋值给httpCon;将httpCon赋值为null
				HttpURLConnection httpCon =
						(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
				//如果httpCon不为nul
				if (httpCon != null) {
					//获取httpCon的响应码
					int code = httpCon.getResponseCode();
					//如果响应码等于200
					if (code == HttpURLConnection.HTTP_OK) {
						//表示资源存在
						return true;
					}
					//如果响应码等于404
					else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
						//表示资源不存在
						return false;
					}
				}
				//con.getContentLength获取传输数据的字节数时, 必须与服务器端协商, 即服务器端必须设置过"content-length"头:
				//如果传输数据的字节数大于0
				if (con.getContentLengthLong() > 0) {
					//表示资源存在
					return true;
				}
				//如果httpCon不为null
				if (httpCon != null) {
					// No HTTP OK status, and no content-length header: give up -- 没有200响应码,也没有contect-length头:放弃
					//断开httpCon的连接
					httpCon.disconnect();
					//表示资源不存在
					return false;
				}
				else {
					// Fall back to stream existence: can we open the stream? 退回到流的存在:我们可以打开流吗?
					//获取输入流,然后关闭流,
					getInputStream().close();
					//经过上面代码,没有抛出异常时,就认为资源存在
					return true;
				}
			}
		}
		catch (IOException ex) {
			//抛出IO异常,就认为资源不存在
			return false;
		}
	}

	@Override
	public boolean isReadable() {
		try {
			//获取URL
			URL url = getURL();
			//如果url指向文件系统的资源,即具有协议'file','vfsfile','vfs'
			if (ResourceUtils.isFileURL(url)) {
				// Proceed with file system resolution 进行文件系统解析
				//获取资源对应的文件
				File file = getFile();
				//如果文件可读 且 文件不是文件夹 就返回true;否则返回false
				return (file.canRead() && !file.isDirectory());
			}
			else {
				// Try InputStream resolution for jar resources 尝试为jar资源进行inputStream解析
				//打开url的连接
				URLConnection con = url.openConnection();
				//定制给定的con,本类实现:如果con的类名是以'JNLP'开头,就意味着这个连接是JNLP,如果是JNLP,就设置连接缓存为true,否则为false;
				//以及,如果con是HttpUrlConnection的子类或本类,会简单设置con的请求方法为'HEAD'
				customizeConnection(con);
				//如果con是HttpURLConnection的子类或本身,
				if (con instanceof HttpURLConnection) {
					//会将con强转赋值给httpCon;
					HttpURLConnection httpCon = (HttpURLConnection) con;
					//获取httpCon的响应码
					int code = httpCon.getResponseCode();
					//如果响应码不等于200
					if (code != HttpURLConnection.HTTP_OK) {
						//关闭连接
						httpCon.disconnect();
						//表示资源不可读
						return false;
					}
				}
				//con.getContentLength获取传输数据的字节数时, 必须与服务器端协商, 即服务器端必须设置过"content-length"头:
				long contentLength = con.getContentLengthLong();
				//如果传输数据的字节数大于0
				if (contentLength > 0) {
					//表示资源可读
					return true;
				}
				//如果传输数据的字节数等于0
				else if (contentLength == 0) {
					// Empty file or directory -> not considered readable... 空文件或文件夹 -> 不认为可读
					//表示资源不可读
					return false;
				}
				else {
					// Fall back to stream existence: can we open the stream? 退回到流的存在:我们可以打开流吗?
					//获取输入流,然后关闭流,
					getInputStream().close();
					//经过上面代码,没有抛出异常时,就认为资源可读
					return true;
				}
			}
		}
		catch (IOException ex) {
			//抛出IO异常,就认为资源不可读
			return false;
		}
	}

	@Override
	public boolean isFile() {
		try {
			//获取资源URL
			URL url = getURL();
			//如果url的协议是以'vfs'开头,意味着url是Vfs资源
			if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				//从Vfs资源委托类中获取url对应的vfs资源对象,再通过vfs资源对象判断是否是一个文件
				//将其判断结果返回出去
				return VfsResourceDelegate.getResource(url).isFile();
			}
			//如果url的协议是'file'表示url是一个文件返回true,否则返回false
			return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol());
		}
		catch (IOException ex) {
			//如果出现IO异常,就认为不是文件,返回false
			return false;
		}
	}

	/**
	 * This implementation returns a File reference for the underlying class path
	 * resource, provided that it refers to a file in the file system.
	 * <p>
	 *     该实现返回一个底层类路径资源的文件引用,前提是它引用文件系统中的文件
	 * </p>
	 * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
	 */
	@Override
	public File getFile() throws IOException {
		//获取资源的URL
		URL url = getURL();
		//如果url的协议是以'vfs'开头,意味着url是Vfs资源
		if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
			//从Vfs资源委托类中获取url对应的vfs资源对象,再通过vfs资源对象获取文件
			//然后返回出去
			return VfsResourceDelegate.getResource(url).getFile();
		}
		//将url解析成文件,即文件系统中的文件,传入desciption只是为在对解析过程抛出的异常进行说明
		return ResourceUtils.getFile(url, getDescription());
	}

	/**
	 * This implementation determines the underlying File
	 * (or jar file, in case of a resource in a jar/zip).
	 * <p>该实现确定底层文件(或者jar/zip中有资源,则会jar文件) </p>
	 */
	@Override
	protected File getFileForLastModifiedCheck() throws IOException {
		//获取资源URL
		URL url = getURL();
		//如果URL是指向一个jar文件的一个资源.即:具有协议'jar','war','zip','vfszip' 或者'wsjar'
		if (ResourceUtils.isJarURL(url)) {
			//从给定的url中提取最外层存储的URL(可能指向一个jar中的一个资源或者一个jar文件本身).
			URL actualUrl = ResourceUtils.extractArchiveURL(url);
			//如果actualUrl的协议是以'vfs'开头,意味着url是Vfs资源
			if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				//从Vfs资源委托类中获取actualUrl对应的vfs资源对象,再通过vfs资源对象获取文件
				//然后返回出去
				return VfsResourceDelegate.getResource(actualUrl).getFile();
			}
			//将url解析成文件,即文件系统中的文件,传入desciption只是为在对解析过程抛出的异常进行说明
			return ResourceUtils.getFile(actualUrl, "Jar URL");
		}
		else {
			//返回底层类路径资源的文件引用,前提是它引用文件系统中的文件
			return getFile();
		}
	}

	/**
	 * This implementation returns a File reference for the given URI-identified
	 * resource, provided that it refers to a file in the file system.
	 * <p>这个实现返回给定URL标识资源的一个文件引用,前提是它引用文件系统中的文件</p>
	 * @since 5.0
	 * @see #getFile(URI)
	 */
	protected boolean isFile(URI uri) {
		try {
			//scheme:获取资源使用的协议,例如http、ftp等,没有默认值
			//如果uri的协议是以'vfs'开头,意味着uri是Vfs资源
			if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
				//从Vfs资源委托类中获取uri对应的vfs资源对象,再通过vfs资源对象判断是否是一个文件
				//将其判断结果返回出去
				return VfsResourceDelegate.getResource(uri).isFile();
			}
			//如果url的协议是'file'表示url是一个文件返回true,否则返回false
			return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme());
		}
		catch (IOException ex) {
			//如果出现IO异常,就认为不是文件,返回false
			return false;
		}
	}

	/**
	 * This implementation returns a File reference for the given URI-identified
	 * resource, provided that it refers to a file in the file system.
	 * <p>这个实现返回给定URL标识资源的一个文件引用,前提是它引用文件系统中的文件</p>
	 * @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String)
	 */
	protected File getFile(URI uri) throws IOException {
		//scheme:获取资源使用的协议,例如http、ftp等,没有默认值
		//如果uri的协议是以'vfs'开头,意味着uri是Vfs资源
		if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
			//从Vfs资源委托类中获取uri对应的vfs资源对象,再通过vfs资源对象获取文件
			//然后返回出去
			return VfsResourceDelegate.getResource(uri).getFile();
		}
		//将url解析成文件,即文件系统中的文件,传入desciption只是为在对解析过程抛出的异常进行说明
		return ResourceUtils.getFile(uri, getDescription());
	}

	/**
	 * This implementation returns a FileChannel for the given URI-identified
	 * resource, provided that it refers to a file in the file system.
	 * <p>这个实现返回给定URL标识资源的一个文件NIO通道,前提是它引用文件系统中的文件</p>
	 * @since 5.0
	 * @see #getFile()
	 */
	@Override
	public ReadableByteChannel readableChannel() throws IOException {
		try {
			// Try file system channel 尝试文件系统通道
			//打开资源对应文件路径的文件系统NIO读取通道
			return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
		}
		catch (FileNotFoundException | NoSuchFileException ex) {
			//FileNotFoundException:表示尝试打开由指定路径名表示的文件失败。这个异常将由
			// 			FileInputStream,FileOutputStream 和 RandomAccessFile 当具有指定路径名的文件
			// 			不存在时,构造函数。如果文件确实存在但由于某种原因存在,这些构造函数也将被抛出,
			// 			例如尝试打开只读文件进行写入时。
			// NoSuchFileException:当尝试访问不存在的文件时,检查的异常抛出
			// Fall back to InputStream adaptation in superclass  退回到父类适配InpputStream
			return super.readableChannel();
		}
	}

	@Override
	public long contentLength() throws IOException {
		//获取资源URL
		URL url = getURL();
		//如果url指向文件系统的资源,即具有协议'file','vfsfile','vfs'
		if (ResourceUtils.isFileURL(url)) {
			// Proceed with file system resolution 进行文件系统解析
			//获取资源文件
			File file = getFile();
			//获取文件大小
			long length = file.length();
			//如果文件大小为0 且 文件不存在
			if (length == 0L && !file.exists()) {
				//抛出文件未找到异常,并加以描述
				throw new FileNotFoundException(getDescription() +
						" cannot be resolved in the file system for checking its content length");
			}
			//返回文件大小
			return length;
		}
		else {
			// Try a URL connection content-length header 尝试使用URL连接的 'content-length' 消息头
			//打开url网络连接
			URLConnection con = url.openConnection();
			//定制给定的con,本类实现:如果con的类名是以'JNLP'开头,就意味着这个连接是JNLP,如果是JNLP,就设置连接缓存为true,否则为false;
			//以及,如果con是HttpUrlConnection的子类或本类,会简单设置con的请求方法为'HEAD'
			customizeConnection(con);
			//获取连接的'content-length' 消息头的值
			return con.getContentLengthLong();
		}
	}

	@Override
	public long lastModified() throws IOException {
		//获取资源的URL
		URL url = getURL();
		//定义文件已检查标记,初始化为false
		boolean fileCheck = false;
		//如果url是指向文件系统的资源,即具有协议'file','vfsfile','vfs' 或者 url
		//			是指向一个jar文件的一个资源。 即:具有协议'jar','war','zip','vfszip'或者'wsjar'
		if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) {
			// Proceed with file system resolution 进行文件系统解析
			//文件已检查标记设置为true
			fileCheck = true;
			try {
				//获取用于时间戳检查的文件
				File fileToCheck = getFileForLastModifiedCheck();
				//获取fileToCheck的最后一次修改时间戳
				long lastModified = fileToCheck.lastModified();
				//如果时间戳大于0 或者 文件存在
				if (lastModified > 0L || fileToCheck.exists()) {
					//返回最后一次修改时间戳
					return lastModified;
				}
			}
			catch (FileNotFoundException ex) {
				// Defensively fall back to URL connection check instead 防御性地退回到URL连接检查
				//捕捉找不到文件异常,不作任何处理,使其进入下面代码块处理
			}
		}
		// Try a URL connection last-modified header 尝试获取URL连接的'last-modified'消息头
		URLConnection con = url.openConnection();
		//定制给定的con,本类实现:如果con的类名是以'JNLP'开头,就意味着这个连接是JNLP,如果是JNLP,就设置连接缓存为true,否则为false;
		//以及,如果con是HttpUrlConnection的子类或本类,会简单设置con的请求方法为'HEAD'
		customizeConnection(con);
		//获取连接的'last-modified'消息头的值(时间戳)
		long lastModified = con.getLastModified();
		//如果文件已检查 且 lastModifed为0 且 'content-length' 消息头的值小于等于0
		if (fileCheck && lastModified == 0 && con.getContentLengthLong() <= 0) {
			//抛出找不到文件异常,并加以描述
			throw new FileNotFoundException(getDescription() +
					" cannot be resolved in the file system for checking its last-modified timestamp");
		}
		//返回连接的'last-modified'消息头的值(时间戳)
		return lastModified;
	}

	/**
	 * Customize the given {@link URLConnection}, obtained in the course of an
	 * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
	 * <p>
	 *	定制给定的{@link URLConnection},在{@link #exists()},{@link #contentLength()}
	 *	或{@link #lastModified()}调用过程中获得
	 * </p>
	 * <p>Calls {@link ResourceUtils#useCachesIfNecessary(URLConnection)} and
	 * delegates to {@link #customizeConnection(HttpURLConnection)} if possible.
	 * Can be overridden in subclasses.
	 * <p>调用{@link ResourceUtils#useCachesIfNecessary(URLConnection)}</p>
	 * @param con the URLConnection to customize
	 * @throws IOException if thrown from URLConnection methods
	 */
	protected void customizeConnection(URLConnection con) throws IOException {
		//如果con的类名是以'JNLP'开头,就意味着这个连接是JNLP,如果是JNLP,就设置连接缓存为true,否则为false。
		ResourceUtils.useCachesIfNecessary(con);
		//如果con是HttpURLConnection的子类或本身
		if (con instanceof HttpURLConnection) {
			//	定制给定的con,本类实现只是简单设置con的请求方法为'HEAD'
			customizeConnection((HttpURLConnection) con);
		}
	}

	/**
	 * Customize the given {@link HttpURLConnection}, obtained in the course of an
	 * {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
	 * <p>
	 *     定制给定的{@link HttpURLConnection},在{@link #exists()},{@link #contentLength()}
	 * 	   或{@link #lastModified()}调用过程中获得
	 * </p>
	 * <p>Sets request method "HEAD" by default. Can be overridden in subclasses.
	 * <p>
	 *     默认情况下请求方法为'HEAD'。可以在子类中覆盖.
	 * </p>
	 * <p> HTTP HEAD : 只请求页面的首部</p>
	 * @param con the HttpURLConnection to customize -- 要定制的HttpURLConnection对象
	 * @throws IOException if thrown from HttpURLConnection methods
	 * 					-- 如果从HttpURLConnection方法中抛出
	 */
	protected void customizeConnection(HttpURLConnection con) throws IOException {
		//设置连接的请求方法为'HEAD'
		con.setRequestMethod("HEAD");
	}


	/**
	 * Inner delegate class, avoiding a hard JBoss VFS API dependency at runtime.
	 * <p>内容委托类,避免了在运行时对JBoss VFS API 的严格依赖</p>
	 */
	private static class VfsResourceDelegate {

		/**
		 * 获取{@code url}的vfs资源对象
		 * @param url vfs协议url
		 * @return {@code url}的vfs资源对象
		 * @throws IOException 获取url 的根资源期间抛出的异常
		 */
		public static Resource getResource(URL url) throws IOException {
			//使用Spring针对JBoss VFS的工具类 反射执行 vfsResource类的getRootUrl(URL)静态方法,
			// 然后得到url的根vfs资源,为该vfs资源对象新建一个VfsResource实例包装起来返回出去
			return new VfsResource(VfsUtils.getRoot(url));
		}

		/**
		 *  获取{@code uri}的vfs资源对象
		 * @param uri vfs协议uri
		 * @return {@code uri}的vfs资源对象
		 * @throws IOException 获取uri 的根资源期间抛出的异常
		 */
		public static Resource getResource(URI uri) throws IOException {
			//使用Spring针对JBoss VFS的工具类 反射执行 vfsResource类的getRootUri(URi)静态方法,
			// 然后得到url的根vfs资源,为该vfs资源对象新建一个VfsResource实例包装起来返回出去
			return new VfsResource(VfsUtils.getRoot(uri));
		}
	}

}

ClassPathResource

/**
 * {@link Resource} implementation for class path resources. Uses either a
 * given {@link ClassLoader} or a given {@link Class} for loading resources.
 * <p>类路径资源的{@link Resource}实现 。使用给定的{@link ClassLoader}或给定的
 * {@link Class}加载资源</p>
 * <p>Supports resolution as {@code java.io.File} if the class path
 * resource resides in the file system, but not for resources in a JAR.
 * Always supports resolution as URL.
 * <p>
 *     如果类路径资源驻留在文件系统中,而不是JAR的资源,则支持解析为
 *     {@code java.io.File}。始终支持解析为URL
 * </p>
 *
 * @author Juergen Hoeller
 * @author Sam Brannen
 * @since 28.12.2003
 * @see ClassLoader#getResourceAsStream(String)
 * @see Class#getResourceAsStream(String)
 */
public class ClassPathResource extends AbstractFileResolvingResource {

	/**
	 * 底层类路径位置,已归一化为Java资源路径
	 * <p>
	 *      归一化:在数学领域,归一化主要是为了数据处理方便提出来的,把数据映射到0~1范围之内处理
	 * 	      而在这里,表示对路径进行修整,是不同系统路径表达式变成同一形式的路径,如Windows系统下的路径分割符是'\',
	 * 	      归一化之后,'\'就会被替换成'/'
	 * </p>
	 */
	private final String path;

	/**
	 * 用于加载 底层类路径位置 的类加载器
	 */
	@Nullable
	private ClassLoader classLoader;

	/**
	 * 用于加载 底层类路径位置 的类
	 */
	@Nullable
	private Class<?> clazz;


	/**
	 * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
	 * A leading slash will be removed, as the ClassLoader resource access
	 * methods will not accept it.
	 * <p>
	 *     使用{@code ClassLoader}为path创建一个新的{@code ClassPathResource}.
	 * 		前导斜杆将被删除,因为ClassLoader资源访问方法不接受它。
	 * </p>
	 * <p>The thread context class loader will be used for
	 * loading the resource.
	 * <p>线程上下文类加载器会被用于加载资源</p>
	 *
	 * @param path the absolute path within the class path -- 类路径中的绝对路径
	 * @see java.lang.ClassLoader#getResourceAsStream(String)
	 * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
	 */
	public ClassPathResource(String path) {
		this(path, (ClassLoader) null);
	}

	/**
	 * Create a new {@code ClassPathResource} for {@code ClassLoader} usage.
	 * A leading slash will be removed, as the ClassLoader resource access
	 * methods will not accept it.
	 * <p>使用ClassLoader为path创建一个新的ClassPathResource. 前导斜杆将被删除,
	 * 因为ClassLoader资源访问方法不接受它。</p>
	 * @param path the absolute path within the classpath -- 类路径中的绝对路径
	 * @param classLoader the class loader to load the resource with,
	 * or {@code null} for the thread context class loader
	 *                    -- 用来加载资源的类加载器,或者 传{@code null} 就使用线程上下文类加载器
	 * @see ClassLoader#getResourceAsStream(String)
	 */
	public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
		//如果path为null
		Assert.notNull(path, "Path must not be null");
		//通过抑制path里的'path/..'和内部简单点之类的序列得到归一化路径
		String pathToUse = StringUtils.cleanPath(path);
		//如果pathToUser是以'/'开头,表示该路径由根路径出发
		if (pathToUse.startsWith("/")) {
			//截取出patchToUse '/'后面的字符串
			pathToUse = pathToUse.substring(1);
		}
		this.path = pathToUse;
		//如果classLoader不为null就引用classloader,否则
		//获取默认类加载器,一般返回线程上下文类加载器,没有就返回加载ClassUtils的类加载器,
		//还是没有就返回系统类加载器,最后还是没有就返回null
		this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	}

	/**
	 * Create a new {@code ClassPathResource} for {@code Class} usage.
	 * The path can be relative to the given class, or absolute within
	 * the classpath via a leading slash.
	 * <p>使用class为path创建一个新的ClassPathResource.该路径能相对于给定的类,或者
	 * 	是绝对路径,在类路径中可以通过前导斜线表示,
	 * </p>
	 * @param path relative or absolute path within the class path
	 *                -- 类路径中的相对或决定路径,传进的path自动归一化成java可解析的路径,
	 *                以保证如window那种'\'形式的路径能被解析
	 * @param clazz the class to load resources with -- 用来加载资源的类
	 * @see java.lang.Class#getResourceAsStream
	 */
	public ClassPathResource(String path, @Nullable Class<?> clazz) {
		//如果path为null,抛出异常
		Assert.notNull(path, "Path must not be null");
		//通过抑制path中的'path/..'和内部简单点之类的序列得到归一化路径
		this.path = StringUtils.cleanPath(path);
		this.clazz = clazz;
	}

	/**
	 * Create a new {@code ClassPathResource} with optional {@code ClassLoader}
	 * and {@code Class}. Only for internal usage.
	 * <p>
	 *     使用可选的{@code ClassLoader}和{@code Class}为path创建一个新的{@code ClassPathResource}.
	 * 	   仅用于内部使用
	 * </p>
	 * @param path relative or absolute path within the classpath -- 类路径中的相对或决定路径
	 * @param classLoader the class loader to load the resource with, if any  -- 用来加载资源的类加载器,如果有
	 * @param clazz the class to load resources with, if any --  -- 用来加载资源的类,如果有
	 * @deprecated as of 4.3.13, in favor of selective use of
	 * {@link #ClassPathResource(String, ClassLoader)} vs {@link #ClassPathResource(String, Class)}
	 * 			-- 从4.3.13版开始,支持有选择地使用{@link #ClassPathResource(String, ClassLoader)} 和
	 * 						{@link #ClassPathResource(String, Class)}
	 */
	@Deprecated
	protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
		//通过抑制path中的'path/..'和内部简单点之类的序列得到归一化路径
		this.path = StringUtils.cleanPath(path);
		this.classLoader = classLoader;
		this.clazz = clazz;
	}


	/**
	 * Return the path for this resource (as resource path within the class path).
	 * <p>返回此资源的路径(作为类路径中的资源路径)</p>
	 */
	public final String getPath() {
		return this.path;
	}

	/**
	 * Return the ClassLoader that this resource will be obtained from.
	 * <p>返回将其获取此资源的类加载器</p>
	 */
	@Nullable
	public final ClassLoader getClassLoader() {
		//如果clazz为null,返回clazz的类加载器;否则返回classLoader
		return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
	}


	/**
	 * This implementation checks for the resolution of a resource URL.
	 * <p>该实现检查资源URL的解析</p>
	 * @see java.lang.ClassLoader#getResource(String)
	 * @see java.lang.Class#getResource(String)
	 */
	@Override
	public boolean exists() {
		return (resolveURL() != null);
	}

	/**
	 * Resolves a URL for the underlying class path resource.
	 * <p>解析底层类路径资源的URL</p>
	 * @return the resolved URL, or {@code null} if not resolvable
	 * 		-- 解析的URL,如果无法解析,就返回{@code null}
	 */
	@Nullable
	protected URL resolveURL() {
		//如果clazz不为null
		if (this.clazz != null) {
			//直接从当前对象的类调用getResource方法, 刚进去就调用了resolveName方法, 这个resolveName方法,
			// 判断文件路径是以/ 开头还是./开头, 来确定是相对与当前class文件目录还是相对于程序根目录,
			// 然后获取class的ClassLoader, 如果获取不到则使用系统自带的ClassLoader. 需要加一个 /表示程序根目录.
			return this.clazz.getResource(this.path);
		}
		//如果classloader不为null
		else if (this.classLoader != null) {
			//classLoader会随着环境的变化而变化, 可以看到第一次直接运行的ClassLoader是jvm自带的
			// classLoader is:sun.misc.Launcher$AppClassLoader@7d05e560, 而在tomcat中是WebappClassLoader,它的父级是
			// java.net.URLClassLoader@1ae8873a, 都能获取到properties文件
			return this.classLoader.getResource(this.path);
		}
		else {
			//使用的是jvm的ClassLoader, 如果是直接运行的Java程序,
			// 那么的确是调用jvm的ClassLoader, 于是调用的程序的根目录是可以获取
			// 这个文件的. 而在tomcat中,这个类并不是由系统自带的ClassLoader装载的,
			// tomcat中而是由一个叫WebappClassLoader来装载的, jvm的ClassLoader取到的
			// 这文件是null
			return ClassLoader.getSystemResource(this.path);
		}
	}

	/**
	 * This implementation opens an InputStream for the given class path resource.
	 * <p>
	 *     该实现打开给定类路径资源的输入流
	 * </p>
	 * @see java.lang.ClassLoader#getResourceAsStream(String)
	 * @see java.lang.Class#getResourceAsStream(String)
	 */
	@Override
	public InputStream getInputStream() throws IOException {
		InputStream is;
		//如果clazz不为null
		if (this.clazz != null) {
			//从class获取资源输入流, 刚进去就调用了resolveName方法, 这个resolveName方法,
			// 判断文件路径是以/ 开头还是./开头, 来确定是相对与当前class文件目录还是相对于程序根目录,
			// 然后获取class的ClassLoader, 如果获取不到则使用系统自带的ClassLoader. 需要加一个 /表示程序根目录.
			is = this.clazz.getResourceAsStream(this.path);
		}
		else if (this.classLoader != null) {
			//从classLoader中获取资源输入流,classLoader会随着环境的变化而变化, 可以看到第一次直接运行的ClassLoader是jvm自带的
			// classLoader is:sun.misc.Launcher$AppClassLoader@7d05e560, 而在tomcat中是WebappClassLoader,它的父级是
			// java.net.URLClassLoader@1ae8873a, 都能获取到properties文件
			is = this.classLoader.getResourceAsStream(this.path);
		}
		else {
			//使用的是jvm的ClassLoader获取资源输入流, 如果是直接运行的Java程序,
			// 那么的确是调用jvm的ClassLoader, 于是调用的程序的根目录是可以获取
			// 这个文件的. 而在tomcat中,这个类并不是由系统自带的ClassLoader装载的,
			// tomcat中而是由一个叫WebappClassLoader来装载的, jvm的ClassLoader取到的
			// 这文件是null
			is = ClassLoader.getSystemResourceAsStream(this.path);
		}
		//如果资源输入流为null
		if (is == null) {
			//抛出未找到文件异常,并加以描述说明
			throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
		}
		//返回输入流
		return is;
	}

	/**
	 * This implementation returns a URL for the underlying class path resource,
	 * if available.
	 * <p>此实现返回基础类路径资源的URL,如果有的话</p>
	 * @see java.lang.ClassLoader#getResource(String)
	 * @see java.lang.Class#getResource(String)
	 */
	@Override
	public URL getURL() throws IOException {
		//解析底层类路径资源的URL
		URL url = resolveURL();
		//如果url为null
		if (url == null) {
			//抛出文件未找到异常,并加以描述
			throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
		}
		//返回底层类路径资源的URL
		return url;
	}

	/**
	 * This implementation creates a ClassPathResource, applying the given path
	 * relative to the path of the underlying resource of this descriptor.
	 * <p>此实现创建一个ClassPathResource,将给定路径应用到相对路对于描述符的底层资源的路径</p>
	 * @see org.springframework.util.StringUtils#applyRelativePath(String, String)
	 */
	@Override
	public Resource createRelative(String relativePath) {
		// 将path应用于relativePath,组装成完整路径
		String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
		//如果clazz不为null,使用class为pathToUse创建一个新的ClassPathResource.否则使用
		//使用classLoader为pathToUse创建一个新的ClassPathResource
		return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
				new ClassPathResource(pathToUse, this.classLoader));
	}

	/**
	 * This implementation returns the name of the file that this class path
	 * resource refers to.
	 * <p>该实现返回该类路径资源引用的文件名</p>
	 * @see org.springframework.util.StringUtils#getFilename(String)
	 */
	@Override
	@Nullable
	public String getFilename() {
		//从path提取文件名,
		return StringUtils.getFilename(this.path);
	}

	/**
	 * This implementation returns a description that includes the class path location.
	 * <p>
	 *     该实现返回包含类路径位置的描述
	 * </p>
	 */
	@Override
	public String getDescription() {
		//新建一个StringBuilder对象,用于拼接资源说明信息
		StringBuilder builder = new StringBuilder("class path resource [");
		//设置要使用的路径为path
		String pathToUse = this.path;
		//如果clazz不为null 且 pathTo不是以'/'开头
		if (this.clazz != null && !pathToUse.startsWith("/")) {
			//提取出clazz的包名,并将包名的'.'替换成'/',再拼接到builder中
			builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
			//将'/'拼接到builder中
			builder.append('/');
		}
		//如果pathToUse是以'/'开头
		if (pathToUse.startsWith("/")) {
			//截取pathToUse第一个位置后面的字符串作为要使用的路径
			pathToUse = pathToUse.substring(1);
		}
		//拼接pathToUse到builder中
		builder.append(pathToUse);
		//拼接']'到builder中
		builder.append(']');
		//将builder的拼接内容转换成String并返回出去
		return builder.toString();
	}


	/**
	 * This implementation compares the underlying class path locations.
	 * <p>
	 *     该实现比较底层类路径路径
	 * </p>
	 */
	@Override
	public boolean equals(@Nullable Object other) {
		//如果other是本对象
		if (this == other) {
			//返回true
			return true;
		}
		//如果other不是ClassPathResource的子类或本身
		if (!(other instanceof ClassPathResource)) {
			//返回false
			return false;
		}
		//将other转换成ClassPathResource对象
		ClassPathResource otherRes = (ClassPathResource) other;
		//如果path等于otherRes的path 且 classLoader等于otherRes的classLoader 且 clazz等于otherRes的clazz
		//就返回true,否则返回false
		return (this.path.equals(otherRes.path) &&
				ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
				ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
	}

	/**
	 * This implementation returns the hash code of the underlying
	 * class path location.
	 * <p>该实现返回底层类路径位置的HashCode</p>
	 */
	@Override
	public int hashCode() {
		return this.path.hashCode();
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值