Spring 5 Resource 源码注释

tech2022-10-31  103

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(); } }
最新回复(0)