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

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

类图如下:

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

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();

 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/sunqingzhong44/article/details/84585097
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞