1. Resource ?
Spring 把所有能记录信息的载体,如各种类型的文件、二进制流等都称为资源,对 Spring 开发者来说,最常用的资源就是 Spring 配置文件(通常是一份 XML 格式的文件)。 Spring 资源访问剖析和策略模式应用(李刚)
2. 为什么 Spring 要搞 Resource ?
一方面:增强 Java 原生的资源访问能力
Spring 需要与各式各样的资源打交道:
然而,用 Java 的 File、URL 访问这些底层资源的步骤过于繁琐。Spring 为资源访问提供了一个 Resource 接口,该接口提供了更强的资源访问能力。
图:Resource 接口
另一方面:对资源进行统一抽象,屏蔽资源访问细节
Resource 接口就是策略模式的典型应用,应用只和 Resource 接口耦合,并不知道底层采用何种资源访问策略,这样应用可以在不同的资源访问策略之间自由切换。
Spring 为 Resource 接口提供了如下实现类:
3. Resource 应用示例
Resource 不仅可在 Spring 的项目中使用,也可直接作为资源访问的工具类使用。意思是说:即使不使用 Spring 框架,也可以使用 Resource 作为工具类,用来代替 URL。当然,使用 Resource 接口会让代码与 Spring 的接口耦合在一起,但这种耦合只是部分工具集的耦合,不会造成太大的代码污染。
示例1:FileSystemResource
import org.apache.commons.io.IOUtils;import org.springframework.core.io.FileSystemResource;
import java.io.IOException;import java.util.Date;
public class ResourceDemo { public static void main(String[] args) throws IOException { FileSystemResource fsr = new FileSystemResource("E:/springdemo/src/main/resources/webj2ee.txt"); System.out.println("exists: "+fsr.exists()); System.out.println("readable: "+fsr.isReadable()); System.out.println("writable: "+fsr.isWritable()); System.out.println("path: "+fsr.getPath()); System.out.println("fileName: "+fsr.getFilename()); System.out.println("lastModified: "+new Date(fsr.lastModified())); System.out.println("description: "+fsr.getDescription()); System.out.println("content: "+IOUtils.toString(fsr.getInputStream(), "UTF-8")); }}
示例2:ClassPathResource
import org.apache.commons.io.IOUtils;import org.springframework.core.io.ClassPathResource;
import java.io.IOException;import java.util.Date;
public class ResourceDemo { public static void main(String[] args) throws IOException {
ClassPathResource fsr = new ClassPathResource("webj2ee/webj2ee.txt"); System.out.println("exists: "+fsr.exists()); System.out.println("readable: "+fsr.isReadable()); System.out.println("isFile: "+fsr.isFile()); System.out.println("path: "+fsr.getPath()); System.out.println("fileName: "+fsr.getFilename()); System.out.println("lastModified: "+new Date(fsr.lastModified())); System.out.println("description: "+fsr.getDescription()); System.out.println("content: "+IOUtils.toString(fsr.getInputStream(), "UTF-8")); }}
示例3:ServletContextResource
package webj2ee;
import org.apache.commons.io.IOUtils;import org.springframework.web.context.support.ServletContextResource;
import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.util.Date;
@WebServlet(name = "SCRDemo",urlPatterns = {"/SCRDemo"})public class SCRDemo extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ServletContextResource fsr = new ServletContextResource(request.getServletContext(), "webj2ee-servletcontext/webj2ee.txt"); System.out.println("exists: " + fsr.exists()); System.out.println("readable: " + fsr.isReadable()); System.out.println("path: " + fsr.getPath()); System.out.println("URL: "+fsr.getURL()); System.out.println("URI: "+fsr.getURI()); System.out.println("fileName: " + fsr.getFilename()); System.out.println("lastModified: " + new Date(fsr.lastModified())); System.out.println("description: " + fsr.getDescription()); System.out.println("content: " + IOUtils.toString(fsr.getInputStream(), "UTF-8")); }}
总结:Spring 提供的各种 Resource 实现类非常实用,避免了直接使用 URL、File 的繁琐、复杂,建议在项目中直接使用。
4. Resource 基本原理
Resource 的各实现类
涉及IO、NIO、JAR、File、Classpath 众多技术细节
留待后续文章详细分析
Resource 总体架构:
InputStreamSource.java 源码:
package org.springframework.core.io;
import java.io.IOException;import java.io.InputStream;
/** * Simple interface for objects that are sources for an {@link InputStream}. * * <p>This is the base interface for Spring's more extensive {@link Resource} interface. * * <p>For single-use streams, {@link InputStreamResource} can be used for any * given {@code InputStream}. Spring's {@link ByteArrayResource} or any * file-based {@code Resource} implementation can be used as a concrete * instance, allowing one to read the underlying content stream multiple times. * This makes this interface useful as an abstract content source for mail * attachments, for example. */public interface InputStreamSource { /** * Return an {@link InputStream} for the content of an underlying resource. * <p>It is expected that each call creates a <i>fresh</i> stream. * <p>This requirement is particularly important when you consider an API such * as JavaMail, which needs to be able to read the stream multiple times when * creating mail attachments. For such a use case, it is <i>required</i> * that each {@code getInputStream()} call returns a fresh stream. * @return the input stream for the underlying resource (must not be {@code null}) * @throws java.io.FileNotFoundException if the underlying resource doesn't exist * @throws IOException if the content stream could not be opened */ InputStream getInputStream() throws IOException;}
Resource.java 源码节选:
package org.springframework.core.io;
import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.URI;import java.net.URL;import java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;
import org.springframework.lang.Nullable;
/** * Interface for a resource descriptor that abstracts from the actual * type of underlying resource, such as a file or class path resource. * * <p>An InputStream can be opened for every resource if it exists in * physical form, but a URL or File handle can just be returned for * certain resources. The actual behavior is implementation-specific. */public interface Resource extends InputStreamSource {
/** * Determine whether this resource actually exists in physical form. * <p>This method performs a definitive existence check, whereas the * existence of a {@code Resource} handle only guarantees a valid * descriptor handle. */ boolean exists();
/** * Indicate whether non-empty contents of this resource can be read via * {@link #getInputStream()}. * <p>Will be {@code true} for typical resource descriptors that exist * since it strictly implies {@link #exists()} semantics as of 5.1. * Note that actual content reading may still fail when attempted. * However, a value of {@code false} is a definitive indication * that the resource content cannot be read. * @see #getInputStream() * @see #exists() */ default boolean isReadable() { return exists(); }
/** * Indicate whether this resource represents a handle with an open stream. * If {@code true}, the InputStream cannot be read multiple times, * and must be read and closed to avoid resource leaks. * <p>Will be {@code false} for typical resource descriptors. */ default boolean isOpen() { return false; }
/** * Determine whether this resource represents a file in a file system. * A value of {@code true} strongly suggests (but does not guarantee) * that a {@link #getFile()} call will succeed. * <p>This is conservatively {@code false} by default. * @since 5.0 * @see #getFile() */ default boolean isFile() { return false; }
/** * Return a URL handle for this resource. * @throws IOException if the resource cannot be resolved as URL, * i.e. if the resource is not available as descriptor */ URL getURL() throws IOException;
/** * Return a URI handle for this resource. * @throws IOException if the resource cannot be resolved as URI, * i.e. if the resource is not available as descriptor * @since 2.5 */ URI getURI() throws IOException;
/** * Return a File handle for this resource. * @throws java.io.FileNotFoundException if the resource cannot be resolved as * absolute file path, i.e. if the resource is not available in a file system * @throws IOException in case of general resolution/reading failures * @see #getInputStream() */ File getFile() throws IOException; /** * Determine the content length for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ long contentLength() throws IOException;
/** * Determine the last-modified timestamp for this resource. * @throws IOException if the resource cannot be resolved * (in the file system or as some other known physical resource type) */ long lastModified() throws IOException;
/** * Create a resource relative to this resource. * @param relativePath the relative path (relative to this resource) * @return the resource handle for the relative resource * @throws IOException if the relative resource cannot be determined */ Resource createRelative(String relativePath) throws IOException;
/** * Determine a filename for this resource, i.e. typically the last * part of the path: for example, "myfile.txt". * <p>Returns {@code null} if this type of resource does not * have a filename. */ @Nullable String getFilename();
/** * Return a description for this resource, * to be used for error output when working with the resource. * <p>Implementations are also encouraged to return this value * from their {@code toString} method. * @see Object#toString() */ String getDescription();}
AbstractResource.java 源码节选:
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 java.nio.channels.Channels;import java.nio.channels.ReadableByteChannel;
import org.springframework.core.NestedIOException;import org.springframework.lang.Nullable;import org.springframework.util.ResourceUtils;
/** * Convenience base class for {@link Resource} implementations, * pre-implementing typical behavior. * * <p>The "exists" method will check whether a File or InputStream can * be opened; "isOpen" will always return false; "getURL" and "getFile" * throw an exception; and "toString" will return the description. * * @author Juergen Hoeller * @since 28.12.2003 */public abstract class AbstractResource implements Resource {
/** * This implementation checks whether a File can be opened, * falling back to whether an InputStream can be opened. * This will cover both directories and content resources. */ @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; } } }
/** * This implementation always returns {@code true} for a resource * that {@link #exists() exists} (revised as of 5.1). */ @Override public boolean isReadable() { return exists(); }
/** * This implementation always returns {@code false}. */ @Override public boolean isOpen() { return false; }
/** * This implementation always returns {@code false}. */ @Override public boolean isFile() { return false; }
/** * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to a URL. */ @Override public URL getURL() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to URL"); }
/** * This implementation builds a URI based on the URL returned * by {@link #getURL()}. */ @Override public URI getURI() throws IOException { URL url = getURL(); try { return ResourceUtils.toURI(url); } catch (URISyntaxException ex) { throw new NestedIOException("Invalid URI [" + url + "]", ex); } }
/** * This implementation throws a FileNotFoundException, assuming * that the resource cannot be resolved to an absolute file path. */ @Override public File getFile() throws IOException { throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path"); }
/** * This implementation returns {@link Channels#newChannel(InputStream)} * with the result of {@link #getInputStream()}. * <p>This is the same as in {@link Resource}'s corresponding default method * but mirrored here for efficient JVM-level dispatching in a class hierarchy. */ @Override public ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(getInputStream()); }
/** * This implementation reads the entire InputStream to calculate the * content length. Subclasses will almost always be able to provide * a more optimal version of this, e.g. checking a File length. * @see #getInputStream() */ @Override public long contentLength() throws IOException { InputStream is = getInputStream(); 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) { } } }
/** * This implementation checks the timestamp of the underlying File, * if available. * @see #getFileForLastModifiedCheck() */ @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; }
/** * Determine the File to use for timestamp checking. * <p>The default implementation delegates to {@link #getFile()}. * @return the File to use for timestamp checking (never {@code null}) * @throws FileNotFoundException if the resource cannot be resolved as * an absolute file path, i.e. is not available in a file system * @throws IOException in case of general resolution/reading failures */ protected File getFileForLastModifiedCheck() throws IOException { return getFile(); }
/** * This implementation throws a FileNotFoundException, assuming * that relative resources cannot be created for this resource. */ @Override public Resource createRelative(String relativePath) throws IOException { throw new FileNotFoundException("Cannot create a relative resource for " + getDescription()); }
/** * This implementation always returns {@code null}, * assuming that this resource type does not have a filename. */ @Override @Nullable public String getFilename() { return null; }
/** * This implementation compares description strings. * @see #getDescription() */ @Override public boolean equals(Object other) { return (this == other || (other instanceof Resource && ((Resource) other).getDescription().equals(getDescription()))); }
/** * This implementation returns the description's hash code. * @see #getDescription() */ @Override public int hashCode() { return getDescription().hashCode(); }
/** * This implementation returns the description of this resource. * @see #getDescription() */ @Override public String toString() { return getDescription(); }}
参考:
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#resources https://www.ibm.com/developerworks/cn/java/j-lo-spring-resource/index.html https://tools.ietf.org/html/rfc3986
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有