Spring源码分析——资源文件的加载

 2019-10-17 21:58  阅读(943)
文章分类:Spring boot

Spring资源文件的读取是通过资源接口Resource的各个实现类提供的,Resource接口抽象了所有sping底层资源,如File,URL, classpath等,对于不同来源的资源文件都有相应的Resource实现如:文件系统资源 FileSystemResource,字节数组资源ByteArrayResource,描述性资源DescriptiveResource,输入流资源InputStreamResource,虚拟文件系统资源VfsResource,类路径资源ClassPathResource,Url资源UrlResource。

类图如下:

20191017100303\_1.png

InputStreamResource接口只定义了一个getInputStream方法,所有的resource都实现了这个方法

Resource接口定义了3个判断当前状态的方法 是否存在exists(),是否可读isReadable(),是否处于打开状态isOpen()。还定义了资源转换为getURL() ,转化为URI的 getURI(),转化为文件的 getFile()。以及获取资源文件名称getFilename(),最后修改时间lastModified(),内容长度contentLength()等属性的方法,为了便于操作提供了创建相对资源的方法createRelative(String relativePath),在错误处理中需要详细的打印错误文件信息,Resource提供了getDescription()获取文件信息

AbstractResource提供一些方法的默认实现:

package org.springframework.core.io;

    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.net.URISyntaxException;
    import java.net.URL;

    import org.springframework.core.NestedIOException;
    import org.springframework.util.Assert;
    import org.springframework.util.ResourceUtils;

    public abstract class AbstractResource implements Resource {

        /**
         *判断文件是否存在
         *如果不存在判断文件是否可以打开
         */
        @Override
        public boolean exists() {
            // Try file existence: can we find the file in the file system?
            try {
                return getFile().exists();
            }
            catch (IOException ex) {
                // Fall back to stream existence: can we open the stream?
                try {
                    getInputStream().close();
                    return true;
                }
                catch (Throwable isEx) {
                    return false;
                }
            }
        }

        /**
         * 直接返回true
         */
        @Override
        public boolean isReadable() {
            return true;
        }

        /**
         * 直接返回false
         */
        @Override
        public boolean isOpen() {
            return false;
        }

        /**
         * 直接抛异常
         */
        @Override
        public URL getURL() throws IOException {
            throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
        }

        /**
         * 先转化为URL,再将转化后的URL转化为URI
         */
        @Override
        public URI getURI() throws IOException {
            URL url = getURL();
            try {
                return ResourceUtils.toURI(url);
            }
            catch (URISyntaxException ex) {
                throw new NestedIOException("Invalid URI [" + url + "]", ex);
            }
        }

        /**
         * 直接抛异常
         */
        @Override
        public File getFile() throws IOException {
            throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
        }

        /**
         * 文件内容长度
         */
        @Override
        public long contentLength() throws IOException {
            InputStream is = getInputStream();
            Assert.state(is != null, "Resource InputStream must not be null");
            try {
                long size = 0;
                byte[] buf = new byte[256];
                int read;
                while ((read = is.read(buf)) != -1) {
                    size += read;
                }
                return size;
            }
            finally {
                try {
                    is.close();
                }
                catch (IOException ex) {
                }
            }
        }

        /**
         * 最后修改时间
         */
        @Override
        public long lastModified() throws IOException {
            File fileToCheck = getFileForLastModifiedCheck();
            long lastModified = fileToCheck.lastModified();
            if (lastModified == 0L && !fileToCheck.exists()) {
                throw new FileNotFoundException(getDescription() +
                        " cannot be resolved in the file system for checking its last-modified timestamp");
            }
            return lastModified;
        }

        /**
         *获取用于计算时间戳的文件
         */
        protected File getFileForLastModifiedCheck() throws IOException {
            return getFile();
        }

        /**
         * 直接抛出异常
         */
        @Override
        public Resource createRelative(String relativePath) throws IOException {
            throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
        }

        /**
         * 返回null
         */
        @Override
        public String getFilename() {
            return null;
        }

        /**
         *toString使用文件猫叔
         */
        @Override
        public String toString() {
            return getDescription();
        }

        /**
         * equals判断是否是同一个资源如果是返回true
         * 如果不是判断文件描述是否相同 如果相同返回true
         * 否则false
         */
        @Override
        public boolean equals(Object obj) {
            return (obj == this ||
                (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
        }

        /**
         * hashCode返回描述的hashcode
         */
        @Override
        public int hashCode() {
            return getDescription().hashCode();
        }

    }

一、文件系统资源 FileSystemResource

文件系统资源 FileSystemResource,资源以文件系统路径的方式表示,这个类除了继承自AbstractResource,还实现了WritableResource,WritableResource接口定义了 isWritable()和getOutputStream()两个方法,表示这个文件有可能可写,没有实现WritableResource的Resource实现类都不可写入操作。

定义了两个final成员变量

private final File file;

    private final String path;

再构造函数中为其赋值:

public FileSystemResource(File file) {
            Assert.notNull(file, "File must not be null");
            this.file = file;
            this.path = StringUtils.cleanPath(file.getPath());
        }

        public FileSystemResource(String path) {
            Assert.notNull(path, "Path must not be null");
            this.file = new File(path);
            this.path = StringUtils.cleanPath(path);
        }

getInputStream()的实现

@Override
        public InputStream getInputStream() throws IOException {
            return new FileInputStream(this.file);
        }

其它方法实现比较简单,值得说明的是equals() 和 hashcode()是根据path来实现的:

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

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

我们可以直接使用FileSystemResource读取硬盘上的文件:

String path = "E:/log.txt";
             Resource r1= new FileSystemResource(path);
             System.out.println("r1 filename: "+r1.getFilename());
             System.out.println("r1 description: "+r1.getDescription());
             InputStream i1 = r1.getInputStream();

             File f = new File("E:/text.txt");
             Resource r2= new FileSystemResource(f);
             System.out.println("r2 filename "+r2.getFilename());
             System.out.println("r2 description: "+r2.getDescription());
             InputStream i2 = r1.getInputStream();

二、字节数组资源ByteArrayResource

  字节数组资源ByteArrayResource,资源就是字节数组。

定义了两个final类型的成员变量:

private final byte[] byteArray;

    private final String description;

在构造函数中赋值:

public ByteArrayResource(byte[] byteArray) {
            this(byteArray, "resource loaded from byte array");
        }

        public ByteArrayResource(byte[] byteArray, String description) {
            Assert.notNull(byteArray, "Byte array must not be null");
            this.byteArray = byteArray;
            this.description = (description != null ? description : "");
        }

getInputStream()的实现:

@Override
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(this.byteArray);
        }

其它方法实现比较简单,值得说明的是equals() 和 hashcode()是根据byteArray来实现的:

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

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

若是读取字节数组资源,可使用这个资源类:

byte[] bytes = "aabbccdd".getBytes();
             Resource r1= new ByteArrayResource(bytes);
             System.out.println("r1 filename: "+r1.getFilename());
             System.out.println("r1 description: "+r1.getDescription());
             InputStream i1 = r1.getInputStream();

             Resource r2= new ByteArrayResource(bytes,"aabbccdd");
             System.out.println("r2 filename "+r2.getFilename());
             System.out.println("r2 description: "+r2.getDescription());
             InputStream i2 = r1.getInputStream();

三、输入流资源InputStreamResource
  输入流资源InputStreamResource,是一个不可变InputStream的包装和一个不可变的描述字符串

InputStreamResource定义了3个成员变量,其中有一个read属性,主要用来控制不可重复性后去资源

private final InputStream inputStream;
    private final String description;
    private boolean read = false;

构造函数:

public InputStreamResource(InputStream inputStream) {
            this(inputStream, "resource loaded through InputStream");
        }

        public InputStreamResource(InputStream inputStream, String description) {
            if (inputStream == null) {
                throw new IllegalArgumentException("InputStream must not be null");
            }
            this.inputStream = inputStream;
            this.description = (description != null ? description : "");
        }

getInputStream()的实现:

public InputStream getInputStream() throws IOException, IllegalStateException {
            if (this.read) {
                throw new IllegalStateException("InputStream has already been read - " +
                        "do not use InputStreamResource if a stream needs to be read multiple times");
            }
            this.read = true;
            return this.inputStream;
        }

equals() 和 hashcode()是根据InputStream来实现的:

@Override
        public boolean equals(Object obj) {
            return (obj == this ||
                (obj instanceof InputStreamResource && ((InputStreamResource) obj).inputStream.equals(this.inputStream)));
        }

        @Override
        public int hashCode() {
            return this.inputStream.hashCode();
        }

四、VFS资源——VfsResource
  vfs是Virtual File System虚拟文件系统,也称为虚拟文件系统开关(Virtual Filesystem Switch).是Linux档案系统对外的接口。任何要使用档案系统的程序都必须经由这层接口来使用它。(摘自百度百科…)它能一致的访问物理文件系统、jar资源、zip资源、war资源等,VFS能把这些资源一致的映射到一个目录上,访问它们就像访问物理文件资源一样,而其实这些资源不存在于物理文件系统。

五、ClassPathResource
这个资源类表示的是类路径下的资源,资源以相对于类路径的方式表示。这个资源类有3个成员变量,分别是一个不可变的相对路径、一个类加载器、一个类对象

getInputStream()方法:

public InputStream getInputStream() throws IOException {
            InputStream is;
            if (this.clazz != null) {
                is = this.clazz.getResourceAsStream(this.path);
            }
            else if (this.classLoader != null) {
                is = this.classLoader.getResourceAsStream(this.path);
            }
            else {
                is = ClassLoader.getSystemResourceAsStream(this.path);
            }
            if (is == null) {
                throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
            }
            return is;
        }

这个我们可以使用它获取classpath下的路径:

String path = "logback.xml";
            Resource r1= new ClassPathResource(path);
            System.out.println("r1 filename: "+r1.getFilename());
            System.out.println("r1 description: "+r1.getDescription()); 
            InputStream i1 = r1.getInputStream();

六、Url资源UrlResource

  UrlResource这个资源类封装了可以以URL表示的各种资源。这个资源类有3个属性,一个URI、一个URL,以及一个规范化后的URL,用于资源间的比较以及计算HashCode。

通过构造方法可以看到,这个资源类基本可以看作java.net.URL的封装。这个资源类的很多方法也都是通过URL或URI操作的。

若是操作URL资源,很明显,这个类比单纯的java.net.URL要好很多

getInputStream方法如下:

@Override
        public InputStream getInputStream() throws IOException {
            URLConnection con = this.url.openConnection();
            ResourceUtils.useCachesIfNecessary(con);
            try {
                return con.getInputStream();
            }
            catch (IOException ex) {
                // Close the HTTP connection (if applicable).
                if (con instanceof HttpURLConnection) {
                    ((HttpURLConnection) con).disconnect();
                }
                throw ex;
            }
        }

我们可以使用它获取uri

String path = "https://www.baidu.com/";
            Resource r1= new UrlResource(path);
            System.out.println("r1 filename: "+r1.getFilename());
            System.out.println("r1 description: "+r1.getDescription()); 
            InputStream i1 = r1.getInputStream();

七、PathResource

主要用来定义基于path的路径,该类注入了Path的引用。该类也是实现了WritableResource接口,也是具有可写能力的,和filesystem很相似,Java7及以上版本可用,

定义了一个成员变量:

private final Path path;

构造函数:

public PathResource(Path path) {
            Assert.notNull(path, "Path must not be null");
            this.path = path.normalize();
        }

        public PathResource(String path) {
            Assert.notNull(path, "Path must not be null");
            this.path = Paths.get(path).normalize();
        }

        public PathResource(URI uri) {
            Assert.notNull(uri, "URI must not be null");
            this.path = Paths.get(uri).normalize();
        }

getInputStream方法如下:

public InputStream getInputStream() throws IOException {
            if (!exists()) {
                throw new FileNotFoundException(getPath() + " (no such file or directory)");
            }
            if (Files.isDirectory(this.path)) {
                throw new FileNotFoundException(getPath() + " (is a directory)");
            }
            return Files.newInputStream(this.path);
        }

我们可以使用读取一些路径下的资源:

String path = "D:/text.txt";
            Resource r1= new PathResource(path);
            System.out.println("r1 filename: "+r1.getFilename());
            System.out.println("r1 description: "+r1.getDescription()); 
            InputStream i1 = r1.getInputStream();

            Resource r2= new PathResource(Paths.get(path));
            System.out.println("r2 filename "+r2.getFilename());
            System.out.println("r2 description: "+r2.getDescription()); 
            InputStream i2 = r1.getInputStream();

来源:[]()

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Spring源码分析——资源文件的加载

相关推荐