子接口ContextResource和WritableResource
这两个接口继承于Resource,拥有Resource的全部方法。其中,ContextResource
接口增加了一个方法:
String getPathWithinContext(); // 返回上下文内的路径
这个方法使得它的实现类有了返回当前上下文路径的能力。
WritableResource
接口增加了2个方法:
boolean isWritable(); // 是否可写 OutputStream getOutputStream() throws IOException; //返回资源的写入流
这个方法使得它的实现类拥有了写资源的能力。
可以看到Spring为我们提供了非常多的实现类。
- ByteArrayResource
- InputStreamResource
- FileSystemResource
- UrlResource
- ClassPathResource
- ServletContextResource
- VfsResource(这个和Jboss有关,本文不讨论)
- …
抽象类AbstractResource
对于任何的接口而言,这个直接抽象类是重中之重,里面浓缩了接口的大部分公共实现,所以这里直接拿源码开刀:
public abstract class AbstractResource implements Resource { // File或者流 都从此处判断 // 这里属于通用实现,子类大都会重写这个方法的~~~~~~ @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 { InputStream is = getInputStream(); is.close(); return true; } catch (Throwable isEx) { return false; } } } // 默认都是可读得。大多数子类都会复写 @Override public boolean isReadable() { return true; } // 默认不是打开的。 比如InputStreamResource就会让他return true @Override public boolean isOpen() { return false; } // 默认不是一个File @Override public boolean isFile() { return false; } // 可议看到getURI方法一般都是依赖于getURL的 @Override public URL getURL() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); } @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 ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); } // 调用此方法,也相当于吧流的read了一遍,请务必注意 @Override public long contentLength() throws IOException { InputStream is = getInputStream(); try { long size = 0; byte[] buf = new byte[255]; 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 { long lastModified = getFileForLastModifiedCheck().lastModified(); if (lastModified == 0L) { throw new FileNotFoundException(getDescription() + " cannot be resolved in the file system for resolving its last-modified timestamp"); } return lastModified; } // 只有一个子类:`AbstractFileResolvingResource`覆盖了此方法 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()); } @Override @Nullable public String getFilename() { return null; } // 这些基础方法,很多子类也都有重写~~~~ 但是一般来说关系不大 @Override public String toString() { return getDescription(); } // 比较的就是getDescription() @Override public boolean equals(Object obj) { return (obj == this || (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription()))); } // getDescription()的hashCode @Override public int hashCode() { return getDescription().hashCode(); } }
那么接下来就以AbstractResource为主要分支,分析它的实现类们:
PathResource
它是基于@since 4.0,也是基于JDK7提供的java.nio.file.Path的。实现原理也非常的简单,更像是对java.nio.file.Path进行了包装(java.nio.file.Files)
另外它还实现了WritableResource,证明它拥有对资源写的能力~~~
ByteArrayResource:获取字节数组封装的资源
ByteArrayResource代表byte[]数组资源,对于“getInputStream”操作将返回一个ByteArrayInputStream。
// @since 1.2.3 public class ByteArrayResource extends AbstractResource { private final byte[] byteArray; private final String description; ... @Override public InputStream getInputStream() throws IOException { return new ByteArrayInputStream(this.byteArray); } }
它可多次读取数组资源,即isOpen()永远返回false
ByteArrayResource因为入参可以是byte[]类型,所以用途比较广泛,可以把从网络或者本地资源都转换为byte[]类型,然后用ByteArrayResource转化为资源
Demo:
public static void main(String[] args) { Resource resource = new ByteArrayResource("Hello!Spring!你好!".getBytes()); if (resource.exists()) { dumpStream(resource); //Hello!Spring!你好! } } // 这个其实可以把这个resource写到本地文件,本处就不麻烦了,直接sout输出看一看即可~~~~ private static void dumpStream(Resource resource) { InputStream is = null; try { //1.获取文件资源 is = resource.getInputStream(); //2.读取资源 byte[] descBytes = new byte[is.available()]; is.read(descBytes); System.out.println(new String(descBytes)); } catch (IOException e) { e.printStackTrace(); } finally { try { //3.关闭资源 is.close(); } catch (IOException e) { } } }
TransformedResource是继承此类的一个扩展。在web中使用较多,实现非常简单,就是多了两个参数:filename和lastModified
FileSystemResource:通过文件系统获取资源
代表java.io.File资源,对于getInputStream操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可多次读取底层文件的字节流。
这个实现类就大名鼎鼎了。此类在Spring5以后,就使用NIO.2的API比如ReadableByteChannel等来操作读写了。提高了效率。这点就和PathResource特别的像了
public class FileSystemResource extends AbstractResource implements WritableResource { private final File file; private final String path; ... // 使用了Files 基于JDK1.7了 @Override public InputStream getInputStream() throws IOException { try { return Files.newInputStream(this.file.toPath()); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } } @Override public boolean isReadable() { return (this.file.canRead() && !this.file.isDirectory()); } // 读写 @Override public ReadableByteChannel readableChannel() throws IOException { try { return FileChannel.open(this.file.toPath(), StandardOpenOption.READ); } catch (NoSuchFileException ex) { throw new FileNotFoundException(ex.getMessage()); } } @Override public WritableByteChannel writableChannel() throws IOException { return FileChannel.open(this.file.toPath(), StandardOpenOption.WRITE); } }
它的工作主要是构建一个File对象出来,此处我就省略了~~~
InputStreamResource:获取输入流封装的资源
InputStreamResource代表java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true。
public class InputStreamResource extends AbstractResource { private final InputStream inputStream; private final String description; private boolean read = false; @Override 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; } }
Demo:
// dumpStream方法请参照上文 public static void main(String[] args) { ByteArrayInputStream bis = new ByteArrayInputStream("Hello World!".getBytes()); Resource resource = new InputStreamResource(bis); if (resource.exists()) { dumpStream(resource); //Hello World! } }