概述

在日常程序开发中,处理外部资源是很繁琐的事情,我们可能需要处理URL资源、File资源资源、ClassPath相关资源、服务器相关资源(JBoss AS 5.x上的VFS资源)等等很多资源。因此处理这些资源需要使用不同的接口,这就增加了我们系统的复杂性;而且处理这些资源步骤都是类似的(打开资源、读取资源、关闭资源),因此如果能抽象出一个统一的接口来对这些底层资源进行统一访问,是不是很方便,而且使我们系统更加简洁,都是对不同的底层资源使用同一个接口进行访问。

Spring 提供一个Resource接口来统一这些底层资源一致的访问,而且提供了一些便利的接口,从而能提供我们的生产力。


代码

  • Resource接口(Spring的Resource接口代表底层外部资源,提供了对底层外部资源的一致性访问接口)
// 资源输入流
public interface InputStreamSource {
    // 获取资源输入流
    InputStream getInputStream() throws IOException;
}
// 资源接口
public interface Resource extends InputStreamSource {
    // 返回当前Resource代表的底层资源是否存在,true表示存在。
    boolean exists();
    // 返回当前Resource代表的底层资源是否可读,true表示可读。
    default boolean isReadable() {
        return this.exists();
    }
    // 返回当前Resource代表的底层资源是否已经打开,如果返回true,则只能被读取一次然后关闭以避免资源泄露;常见的Resource实现一般返回false。
    default boolean isOpen() {
        return false;
    }
    // 返回当前Resource代表的底层资源是否是文件
    default boolean isFile() {
        return false;
    }
    // 如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IOException。
    URL getURL() throws IOException;
   // 如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IOException。
    URI getURI() throws IOException;
   // 如果当前Resource代表的底层资源能由java.io.File代表,则返回该File,否则抛出IOException。
    File getFile() throws IOException;

    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }
    // 返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度。
    long contentLength() throws IOException;
    // 返回当前Resource代表的底层资源的最后修改时间。
    long lastModified() throws IOException;
    // 用于创建相对于当前Resource代表的底层资源的资源,比如当前Resource代表文件资源“d:/test/”则createRelative(“test.txt”)将返回表文件资源“d:/test/test.txt”Resource资源。
    Resource createRelative(String var1) throws IOException;
    // 返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://d:/test.txt”将返回“d:/test.txt”,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径。
    @Nullable
    String getFilename();
    // 返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)。
    String getDescription();
}

Resource接口提供了足够的抽象,足够满足我们日常使用。而且提供了很多内置Resource实现:ByteArrayResource、InputStreamResource 、FileSystemResource 、UrlResource 、ClassPathResource、ServletContextResource、VfsResource等。

  • AbstractResource抽象类
// 资源抽象类
public abstract class AbstractResource implements Resource {
    // 构造器
    public AbstractResource() {
    }
    // 判断当前资源是否存在
    public boolean exists() {
        Log logger;
        // 判断当前资源是否是文件
        if (this.isFile()) {
            try {
               // 判断当前文件是否存在
                return this.getFile().exists();
            } catch (IOException var4) {
                logger = LogFactory.getLog(this.getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not retrieve File for existence check of " + this.getDescription(), var4);
                }
            }
        }

        try {
            // 关闭资源输入流
            this.getInputStream().close();
            return true;
        } catch (Throwable var3) {
            logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Could not retrieve InputStream for existence check of " + this.getDescription(), var3);
            }

            return false;
        }
    }
    // 是否可读
    public boolean isReadable() {
        // 当前文件存在则就可读
        return this.exists();
    }
    // 资源是否以及打开
    public boolean isOpen() {
        // 默认是false
        return false;
    }
    // 资源是否是文件
    public boolean isFile() {
        // 默认时候false
        return false;
    }
    // 获取资源代表的URL 
    public URL getURL() throws IOException {
        throw new FileNotFoundException(this.getDescription() + " cannot be resolved to URL");
    }
    // 获取资源代表的URI 
    public URI getURI() throws IOException {
        // 获取URL
        URL url = this.getURL();

        try {
           // 获取URL中的URI
            return ResourceUtils.toURI(url);
        } catch (URISyntaxException var3) {
            throw new NestedIOException("Invalid URI [" + url + "]", var3);
        }
    }
    // 获取资源代表的文件
    public File getFile() throws IOException {
        throw new FileNotFoundException(this.getDescription() + " cannot be resolved to absolute file path");
    }

    public ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }
    // 获取文件资源长度
    public long contentLength() throws IOException {
        // 获取文件资源输入流
        InputStream is = this.getInputStream();
        boolean var16 = false;
        // 记录文件长度的var6
        long var6;
        try {
            var16 = true;
            // 临时记录文件长度的size
            long size = 0L;
            // 字节缓冲区
            byte[] buf = new byte[256];
            // 循环读
            while(true) {
                int read;
                // 如果文件中没有字节了
                if ((read = is.read(buf)) == -1) {
                    // 将临时记录赋值给var6
                    var6 = size;
                    // var16赋值为false
                    var16 = false;
                    // 结束循环
                    break;
                }
                // 累计
                size += (long)read;
            }
        } finally {
            if (var16) {
                try {
                    is.close();
                } catch (IOException var17) {
                    Log logger = LogFactory.getLog(this.getClass());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not close content-length InputStream for " + this.getDescription(), var17);
                    }
                }

            }
        }

        try {
            is.close();
        } catch (IOException var18) {
            Log logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Could not close content-length InputStream for " + this.getDescription(), var18);
            }
        }

        return var6;
    }
    // 获取资源最后修改时间
    public long lastModified() throws IOException {
        // 获取当前资源文件
        File fileToCheck = this.getFileForLastModifiedCheck();
        // 获取资源文件最后修改时间
        long lastModified = fileToCheck.lastModified();
        // 如果最后修改时间等于0同时文件不存在
        if (lastModified == 0L && !fileToCheck.exists()) {
            throw new FileNotFoundException(this.getDescription() + " cannot be resolved in the file system for checking its last-modified timestamp");
        } else {
            return lastModified;
        }
    }

   // 获取文件最后最改时间
    protected File getFileForLastModifiedCheck() throws IOException {
        return this.getFile();
    }

    public Resource createRelative(String relativePath) throws IOException {
        throw new FileNotFoundException("Cannot create a relative resource for " + this.getDescription());
    }
    // 获取资源文件名称
    @Nullable
    public String getFilename() {
        return null;
    }

    public boolean equals(@Nullable Object other) {
        return this == other || other instanceof Resource && ((Resource)other).getDescription().equals(this.getDescription());
    }

    public int hashCode() {
        return this.getDescription().hashCode();
    }

    public String toString() {
        return this.getDescription();
    }
}
  • 具体实现类
    1.ByteArrayResource实现类
// 字节数组资源
public class ByteArrayResource extends AbstractResource {
    // 字节数组
    private final byte[] byteArray;
    // 描述
    private final String description;
    // 构造器(字节数组)
    public ByteArrayResource(byte[] byteArray) {
        this(byteArray, "resource loaded from byte array");
    }
    // 构造器(字节数组,描述)
    public ByteArrayResource(byte[] byteArray, @Nullable String description) {
       // 成员变量进行初始化
        Assert.notNull(byteArray, "Byte array must not be null");
        this.byteArray = byteArray;
        this.description = description != null ? description : "";
    }
    // 获取字节数组
    public final byte[] getByteArray() {
        return this.byteArray;
    }
    // 判断是否为空(一般判断的是文件)
    public boolean exists() {
       // 默认是true
        return true;
    }
    // 返回字节长度
    public long contentLength() {
        return (long)this.byteArray.length;
    }
    // 获取资源输入流
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.byteArray);
    }
    // 获取资源描述
    public String getDescription() {
        return "Byte array resource [" + this.description + "]";
    }

    public boolean equals(@Nullable Object other) {
        return this == other || other instanceof ByteArrayResource && Arrays.equals(((ByteArrayResource)other).byteArray, this.byteArray);
    }

    public int hashCode() {
        return byte[].class.hashCode() * 29 * this.byteArray.length;
    }
}

2.FileSystemResource实现类

public class FileSystemResource extends AbstractResource implements WritableResource {
    // 资源路径
    private final String path;
    // 资源文件
    @Nullable
    private final File file;
    // 资源文件路径对象
    private final Path filePath;
    // 构造器(资源路径)
    public FileSystemResource(String path) {
        // 初始化成员变量
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.file = new File(path);
        this.filePath = this.file.toPath();
    }
     // 构造器(资源文件对象)
    public FileSystemResource(File file) {
        // 初始化成员变量
        Assert.notNull(file, "File must not be null");
        this.path = StringUtils.cleanPath(file.getPath());
        this.file = file;
        this.filePath = file.toPath();
    }
    // 构造器(资源路径对象)
    public FileSystemResource(Path filePath) {
        // 初始化成员变量
        Assert.notNull(filePath, "Path must not be null");
        this.path = StringUtils.cleanPath(filePath.toString());
        this.file = null;
        this.filePath = filePath;
    }
    // 构造器(资源文件系统对象,资源路径)
    public FileSystemResource(FileSystem fileSystem, String path) {
        // 初始化成员变量
        Assert.notNull(fileSystem, "FileSystem must not be null");
        Assert.notNull(path, "Path must not be null");
        this.path = StringUtils.cleanPath(path);
        this.file = null;
        this.filePath = fileSystem.getPath(this.path).normalize();
    }
    // 获取资源路径
    public final String getPath() {
        return this.path;
    }
    // 判断资源文件是否存在
    public boolean exists() {
        return this.file != null ? this.file.exists() : Files.exists(this.filePath, new LinkOption[0]);
    }
    // 判断资源文件是否可读
    public boolean isReadable() {
        return this.file != null ? this.file.canRead() && !this.file.isDirectory() : Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath, new LinkOption[0]);
    }
    // 获取资源文件输入流
    public InputStream getInputStream() throws IOException {
        try {
            return Files.newInputStream(this.filePath);
        } catch (NoSuchFileException var2) {
            throw new FileNotFoundException(var2.getMessage());
        }
    }
    // 是否可写
    public boolean isWritable() {
        return this.file != null ? this.file.canWrite() && !this.file.isDirectory() : Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath, new LinkOption[0]);
    }
    // 获取资源文件输出流
    public OutputStream getOutputStream() throws IOException {
        return Files.newOutputStream(this.filePath);
    }
    // 获取资源代表的URL
    public URL getURL() throws IOException {
        return this.file != null ? this.file.toURI().toURL() : this.filePath.toUri().toURL();
    }
    // 获取资源代表的URI
    public URI getURI() throws IOException {
        return this.file != null ? this.file.toURI() : this.filePath.toUri();
    }
    // 判断是否是文件
    public boolean isFile() {
       // 默认是true
        return true;
    }
    // 获取资源文件
    public File getFile() {
        return this.file != null ? this.file : this.filePath.toFile();
    }
    // 获取读通道
    public ReadableByteChannel readableChannel() throws IOException {
        try {
            return FileChannel.open(this.filePath, StandardOpenOption.READ);
        } catch (NoSuchFileException var2) {
            throw new FileNotFoundException(var2.getMessage());
        }
    }
    // 获取写通道
    public WritableByteChannel writableChannel() throws IOException {
        return FileChannel.open(this.filePath, StandardOpenOption.WRITE);
    }
   // 获取文件长度
    public long contentLength() throws IOException {
        if (this.file != null) {
            long length = this.file.length();
            if (length == 0L && !this.file.exists()) {
                throw new FileNotFoundException(this.getDescription() + " cannot be resolved in the file system for checking its content length");
            } else {
                return length;
            }
        } else {
            try {
                return Files.size(this.filePath);
            } catch (NoSuchFileException var3) {
                throw new FileNotFoundException(var3.getMessage());
            }
        }
    }
   // 获取文件最后修改时间
    public long lastModified() throws IOException {
        if (this.file != null) {
            return super.lastModified();
        } else {
            try {
                return Files.getLastModifiedTime(this.filePath).toMillis();
            } catch (NoSuchFileException var2) {
                throw new FileNotFoundException(var2.getMessage());
            }
        }
    }

    public Resource createRelative(String relativePath) {
        String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
        return this.file != null ? new FileSystemResource(pathToUse) : new FileSystemResource(this.filePath.getFileSystem(), pathToUse);
    }
    // 获取资源文件名称
    public String getFilename() {
        return this.file != null ? this.file.getName() : this.filePath.getFileName().toString();
    }
    // 获取资源文件描述
    public String getDescription() {
        return "file [" + (this.file != null ? this.file.getAbsolutePath() : this.filePath.toAbsolutePath()) + "]";
    }

    public boolean equals(@Nullable Object other) {
        return this == other || other instanceof FileSystemResource && this.path.equals(((FileSystemResource)other).path);
    }

    public int hashCode() {
        return this.path.hashCode();
    }
}