02、Spring源码分析 - 02-Resource

Spring的Resource接口为资源访问提供了统一的接口,不同的实现类实现了从不同上下文获取资源。下面是该接口的方法:

public interface Resource extends InputStreamSource {
	boolean exists();
	default boolean isReadable() {
		return true;
	}
	default boolean isOpen() {
		return false;
	}
	default boolean isFile() {
		return false;
	}
	URL getURL() throws IOException;
	URI getURI() throws IOException;
	File getFile() throws IOException;
	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}
	long contentLength() throws IOException;
	long lastModified() throws IOException;
	Resource createRelative(String relativePath) throws IOException;
	@Nullable
	String getFilename();
	String getDescription();
}
public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

 

下面分析几个常用的Resource实现类。

1、 AbstractResource提供了Resource的默认实现;

public abstract class AbstractResource implements Resource {
   @Override
   public boolean exists() {
      try {
         return getFile().exists();
      }
      catch (IOException ex) {
         try {
            InputStream is = getInputStream();
            is.close();
            return true;
         }
         catch (Throwable isEx) {
            return false;
         }
      }
   }
   @Override
   public boolean isReadable() {
      return true;
   }
   @Override
   public boolean isOpen() {
      return false;
   }
   @Override
   public boolean isFile() {
      return false;
   }
   @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());
   }
   @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;
   }
   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();
   }
   @Override
   public boolean equals(Object obj) {
      return (obj == this ||
         (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
   }
   @Override
   public int hashCode() {
      return getDescription().hashCode();
   }
}

2、 UrlResource封装了一个java.net.URL对象,用来访问URL可以正常访问的任意对象,比如文件;

public class UrlResource extends AbstractFileResolvingResource {
   @Nullable
   private final URI uri;
   private final URL url;
   private final URL cleanedUrl;
   public UrlResource(URI uri) throws MalformedURLException {
      Assert.notNull(uri, "URI must not be null");
      this.uri = uri;
      this.url = uri.toURL();
      this.cleanedUrl = getCleanedUrl(this.url, uri.toString());
   }
   public UrlResource(URL url) {
      Assert.notNull(url, "URL must not be null");
      this.url = url;
      this.cleanedUrl = getCleanedUrl(this.url, url.toString());
      this.uri = null;
   }
   public UrlResource(String path) throws MalformedURLException {
      Assert.notNull(path, "Path must not be null");
      this.uri = null;
      this.url = new URL(path);
      this.cleanedUrl = getCleanedUrl(this.url, path);
   }
   public UrlResource(String protocol, String location) throws MalformedURLException  {
      this(protocol, location, null);
   }
   public UrlResource(String protocol, String location, @Nullable String fragment) throws MalformedURLException  {
      try {
         this.uri = new URI(protocol, location, fragment);
         this.url = this.uri.toURL();
         this.cleanedUrl = getCleanedUrl(this.url, this.uri.toString());
      }
      catch (URISyntaxException ex) {
         MalformedURLException exToThrow = new MalformedURLException(ex.getMessage());
         exToThrow.initCause(ex);
         throw exToThrow;
      }
   }
   private URL getCleanedUrl(URL originalUrl, String originalPath) {
      try {
         return new URL(StringUtils.cleanPath(originalPath));
      }
      catch (MalformedURLException ex) {
         // Cleaned URL path cannot be converted to URL
         // -> take original URL.
         return originalUrl;
      }
   }
   @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;
      }
   }
   @Override
   public URL getURL() {
      return this.url;
   }
   @Override
   public URI getURI() throws IOException {
      if (this.uri != null) {
         return this.uri;
      }
      else {
         return super.getURI();
      }
   }
   @Override
   public boolean isFile() {
      if (this.uri != null) {
         return super.isFile(this.uri);
      }
      else {
         return super.isFile();
      }
   }
   @Override
   public File getFile() throws IOException {
      if (this.uri != null) {
         return super.getFile(this.uri);
      }
      else {
         return super.getFile();
      }
   }
   @Override
   public Resource createRelative(String relativePath) throws MalformedURLException {
      if (relativePath.startsWith("/")) {
         relativePath = relativePath.substring(1);
      }
      return new UrlResource(new URL(this.url, relativePath));
   }
   @Override
   public String getFilename() {
      return StringUtils.getFilename(this.cleanedUrl.getPath());
   }
   @Override
   public String getDescription() {
      return "URL [" + this.url + "]";
   }
   @Override
   public boolean equals(Object obj) {
      return (obj == this ||
         (obj instanceof UrlResource && this.cleanedUrl.equals(((UrlResource) obj).cleanedUrl)));
   }
   @Override
   public int hashCode() {
      return this.cleanedUrl.hashCode();
   }
}

3、 可以使用ClassPathResource来获取类路径上的资源ClassPathResource可以使用线程上下文的加载器、调用者提供的加载器或指定的类中的任意一个来加载资源;

public class ClassPathResource extends AbstractFileResolvingResource {
   private final String path;
   @Nullable
   private ClassLoader classLoader;
   @Nullable
   private Class<?> clazz;
   public ClassPathResource(String path) {
      this(path, (ClassLoader) null);
   }
   public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
      Assert.notNull(path, "Path must not be null");
      String pathToUse = StringUtils.cleanPath(path);
      if (pathToUse.startsWith("/")) {
         pathToUse = pathToUse.substring(1);
      }
      this.path = pathToUse;
      this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
   }
   public ClassPathResource(String path, @Nullable Class<?> clazz) {
      Assert.notNull(path, "Path must not be null");
      this.path = StringUtils.cleanPath(path);
      this.clazz = clazz;
   }
   @Deprecated
   protected ClassPathResource(String path, @Nullable ClassLoader classLoader, @Nullable Class<?> clazz) {
      this.path = StringUtils.cleanPath(path);
      this.classLoader = classLoader;
      this.clazz = clazz;
   }
   public final String getPath() {
      return this.path;
   }
   @Nullable
   public final ClassLoader getClassLoader() {
      return (this.clazz != null ? this.clazz.getClassLoader() : this.classLoader);
   }
   @Override
   public boolean exists() {
      return (resolveURL() != null);
   }
   @Nullable
   protected URL resolveURL() {
      if (this.clazz != null) {
         return this.clazz.getResource(this.path);
      }
      else if (this.classLoader != null) {
         return this.classLoader.getResource(this.path);
      }
      else {
         return ClassLoader.getSystemResource(this.path);
      }
   }
   @Override
   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;
   }
   @Override
   public URL getURL() throws IOException {
      URL url = resolveURL();
      if (url == null) {
         throw new FileNotFoundException(getDescription() + " cannot be resolved to URL because it does not exist");
      }
      return url;
   }
   @Override
   public Resource createRelative(String relativePath) {
      String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
      return (this.clazz != null ? new ClassPathResource(pathToUse, this.clazz) :
            new ClassPathResource(pathToUse, this.classLoader));
   }
   @Override
   @Nullable
   public String getFilename() {
      return StringUtils.getFilename(this.path);
   }
   @Override
   public String getDescription() {
      StringBuilder builder = new StringBuilder("class path resource [");
      String pathToUse = path;
      if (this.clazz != null && !pathToUse.startsWith("/")) {
         builder.append(ClassUtils.classPackageAsResourcePath(this.clazz));
         builder.append('/');
      }
      if (pathToUse.startsWith("/")) {
         pathToUse = pathToUse.substring(1);
      }
      builder.append(pathToUse);
      builder.append(']');
      return builder.toString();
   }
   @Override
   public boolean equals(Object obj) {
      if (obj == this) {
         return true;
      }
      if (obj instanceof ClassPathResource) {
         ClassPathResource otherRes = (ClassPathResource) obj;
         return (this.path.equals(otherRes.path) &&
               ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) &&
               ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz));
      }
      return false;
   }
   @Override
   public int hashCode() {
      return this.path.hashCode();
   }
}

4、 FileSystemResource是针对java.io.File提供的Resource实现;

public class FileSystemResource extends AbstractResource implements WritableResource {
   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);
   }
   public final String getPath() {
      return this.path;
   }
   @Override
   public boolean exists() {
      return this.file.exists();
   }
   @Override
   public boolean isReadable() {
      return (this.file.canRead() && !this.file.isDirectory());
   }
   @Override
   public InputStream getInputStream() throws IOException {
      try {
         return Files.newInputStream(this.file.toPath());
      }
      catch (NoSuchFileException ex) {
         throw new FileNotFoundException(ex.getMessage());
      }
   }
   @Override
   public boolean isWritable() {
      return (this.file.canWrite() && !this.file.isDirectory());
   }
   @Override
   public OutputStream getOutputStream() throws IOException {
      return Files.newOutputStream(this.file.toPath());
   }
   @Override
   public URL getURL() throws IOException {
      return this.file.toURI().toURL();
   }
   @Override
   public URI getURI() throws IOException {
      return this.file.toURI();
   }
   @Override
   public boolean isFile() {
      return true;
   }
   @Override
   public File getFile() {
      return this.file;
   }
   @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);
   }
   @Override
   public long contentLength() throws IOException {
      return this.file.length();
   }
   @Override
   public Resource createRelative(String relativePath) {
      String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
      return new FileSystemResource(pathToUse);
   }
   @Override
   public String getFilename() {
      return this.file.getName();
   }
   @Override
   public String getDescription() {
      return "file [" + this.file.getAbsolutePath() + "]";
   }
   @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();
   }
}

5、 ServletContextResource为了获取web根路径的ServletContext资源而提供的Resource实现;

public class ServletContextResource extends AbstractFileResolvingResource implements ContextResource {
   private final ServletContext servletContext;
   private final String path;
   public ServletContextResource(ServletContext servletContext, String path) {
      // check ServletContext
      Assert.notNull(servletContext, "Cannot resolve ServletContextResource without ServletContext");
      this.servletContext = servletContext;

      // check path
      Assert.notNull(path, "Path is required");
      String pathToUse = StringUtils.cleanPath(path);
      if (!pathToUse.startsWith("/")) {
         pathToUse = "/" + pathToUse;
      }
      this.path = pathToUse;
   }
   public final ServletContext getServletContext() {
      return this.servletContext;
   }
   public final String getPath() {
      return this.path;
   }
   @Override
   public boolean exists() {
      try {
         URL url = this.servletContext.getResource(this.path);
         return (url != null);
      }
      catch (MalformedURLException ex) {
         return false;
      }
   }
   @Override
   public boolean isReadable() {
      InputStream is = this.servletContext.getResourceAsStream(this.path);
      if (is != null) {
         try {
            is.close();
         }
         catch (IOException ex) {
            // ignore
         }
         return true;
      }
      else {
         return false;
      }
   }
   @Override
   public boolean isFile() {
      try {
         URL url = this.servletContext.getResource(this.path);
         if (url != null && ResourceUtils.isFileURL(url)) {
            return true;
         }
         else {
            return (this.servletContext.getRealPath(this.path) != null);
         }
      }
      catch (MalformedURLException ex) {
         return false;
      }
   }
   @Override
   public InputStream getInputStream() throws IOException {
      InputStream is = this.servletContext.getResourceAsStream(this.path);
      if (is == null) {
         throw new FileNotFoundException("Could not open " + getDescription());
      }
      return is;
   }
   @Override
   public URL getURL() throws IOException {
      URL url = this.servletContext.getResource(this.path);
      if (url == null) {
         throw new FileNotFoundException(
               getDescription() + " cannot be resolved to URL because it does not exist");
      }
      return url;
   }
   @Override
   public File getFile() throws IOException {
      URL url = this.servletContext.getResource(this.path);
      if (url != null && ResourceUtils.isFileURL(url)) {
         // Proceed with file system resolution...
         return super.getFile();
      }
      else {
         String realPath = WebUtils.getRealPath(this.servletContext, this.path);
         return new File(realPath);
      }
   }
   @Override
   public Resource createRelative(String relativePath) {
      String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
      return new ServletContextResource(this.servletContext, pathToUse);
   }
   @Override
   @Nullable
   public String getFilename() {
      return StringUtils.getFilename(this.path);
   }
   @Override
   public String getDescription() {
      return "ServletContext resource [" + this.path + "]";
   }
   @Override
   public String getPathWithinContext() {
      return this.path;
   }
   @Override
   public boolean equals(Object obj) {
      if (obj == this) {
         return true;
      }
      if (obj instanceof ServletContextResource) {
         ServletContextResource otherRes = (ServletContextResource) obj;
         return (this.servletContext.equals(otherRes.servletContext) && this.path.equals(otherRes.path));
      }
      return false;
   }
   @Override
   public int hashCode() {
      return this.path.hashCode();
   }
}

6、 在确实没有找到其他合适的Resource实现时,才使用InputSteamResource;

public class InputStreamResource extends AbstractResource {
   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, @Nullable String description) {
      Assert.notNull(inputStream, "InputStream must not be null");
      this.inputStream = inputStream;
      this.description = (description != null ? description : "");
   }
   @Override
   public boolean exists() {
      return true;
   }
   @Override
   public boolean isOpen() {
      return true;
   }
   @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;
   }
   @Override
   public String getDescription() {
      return "InputStream resource [" + this.description + "]";
   }
   @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();
   }
}

7、 当需要从字节数组加载内容时,ByteArrayResource是一个不错的选择,使用ByteArrayResource可以不用求助于InputStreamResource;

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;
   }
   @Override
   public boolean exists() {
      return true;
   }
   @Override
   public long contentLength() {
      return this.byteArray.length;
   }
   @Override
   public InputStream getInputStream() throws IOException {
      return new ByteArrayInputStream(this.byteArray);
   }
   @Override
   public String getDescription() {
      return "Byte array resource [" + this.description + "]";
   }
   @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);
   }
}