Spring中资源的加载原来是这么一回事啊!

1. 简介

在JDK中 java.net.URL 适用于加载资源的类,但是 URL 的实现类都是访问网络资源的,并没有可以从类路径或者相对路径获取文件及 ServletContext , 虽然可以通过自定义扩展URL接口来实现新的处理程序,但是这是非常复杂的,同时 URL 接口中定义的行为也并不是很理想的 ,如检测资源的存在等的行为,这也是 spring 为什么自己全新的开发一套自己的资源加载策略, 同时它也满足下面的特点:

  • 单一职责原则,将资源的定义和资源的加载的模型界限划的非常清晰
  • 采用高度抽象,统一的资源定义和资源加载的策略和行为,资源加载返回给客户端的是抽象的资源,客户端根据资源的行为定义对其进行具体化的处理

2. Resource 接口

spring 中的 Resource 接口目的在于成为一种功能更加强大的接口,用于抽象化对具体资源的访问,它继承了 org.springframework.core.io.InputStreamSource 接口,作为资源定义的顶级接口, Resource 内部定义了通用的方法,并且有它的子类 AbstractResource 来提供统一的默认实现,

Resouerce 接口定义:

//资源定义接口
public interface Resource extends InputStreamSource {

    /**
     * 检验资源是否是物理存在
     */
    boolean exists();

    /**
     * 判断资源是否是可读的
     */
    default boolean isReadable() {
        return exists();
    }

    /**
     * 判断资源是否是打开的,true为打开
     */
    default boolean isOpen() {
        return false;
    }

    /**
     * 判断该资源是否是文件 true为是
     */
    default boolean isFile() {
        return false;
    }

    /**
     * 返回该资源的URL句柄
     */
    URL getURL() throws IOException;

    /**
     * 返回该资源的URI句柄
     */
    URI getURI() throws IOException;

    /**
     * 获取该资源的File句柄
     */
    File getFile() throws IOException;

    /**
     * 返回一个ReadableByteChannel 作为NIO中的可读通道
     */
    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();

}

InputStreamSource 接口定义:

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 中一些最重要的方法:

  • getInputStream() :找到并打开资源,并返回一个资源以 InputStream 供读取,每次调用都会返回一个新的 InputStream ,调用者有责任关闭流

  • exists() :返回 boolean 指示此资源是否实际以物理形式存在。

  • isOpen() :返回, boolean 指示此资源是否表示具有打开流的句柄, 如果为 trueInputStream 则不能多次读取,必须只读取一次,然后将其关闭以避免资源泄漏。返回 false 所有常用资源实现(除外) InputStreamResource 可读

  • getDescription() :返回对此资源的描述,以便在使用该资源时用于错误输出。这通常是标准文件名或资源的实际 URL

    ** Resource 实现**

Resource类图

  • UrlResource : 包装一个 java.net.URL ,可用于访问通常可以通过 URL 访问的任何对象,例如文件, HTTP 目标, FTP 目标等。所有 URL 都有一个标准化的 String 表示形式,因此适当的标准化前缀可用于指示另一种 URL 类型。如: file : 访问文件系统路径, http : 通过 HTTP 协议 ftp : 访问资源,通过 FTP 访问资源等

  • ClassPathResource : 此类表示应从类路径获取的资源。它使用线程上下文类加载器( ClassLoader ),给定的类加载器或给定的类来加载资源

  • FileSystemResource : 是一个 Resource 执行 java.io.Filejava.nio.file.Path 类型资源的封装,它支持 FileURL , 实现 WritableResource 接口,且从 Spring Framework 5.0 开始, FileSystemResource 使用 NIO2 API 进行读/写交互

  • ServletContextResource : 该 ServletContex t资源解释相关 Web 应用程序的根目录内的相对路径。

  • InputStreamResource : 将给定的 InputStream 作为一种资源的 Resource 的实现类

  • ByteArrayResource : 这是Resource给定字节数组的实现。它为给定的字节数组创建一个 ByteArrayInputStream

3. ResourceLoader 接口

ResourceLoader 主要是用于返回(即加载) Resource 对象,主要定义:

public interface ResourceLoader {

    /** Pseudo URL prefix for loading from the class path: "classpath:". */
    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;

    /**
     * 返回指定路径的资源处理器
     * 必须支持完全限定的网址: "file:C:/test.dat"
     * 必须支持ClassPath 的 URL  :"classpath:test.dat"
     * 必须支持相对路径  : "WEB-INF/test.dat"
     * 并不能保证资源是否物理存在,需要自己去检测通过existence
     * 再spring中所有的应用上下文都去实现这个接口,可以进行资源的加载
     */
    Resource getResource(String location);

    /**
     * 返回当前类的 ClassLoader 对象
     */
    @Nullable
    ClassLoader getClassLoader();

}
  • 应用上下文即容器都有实现 ResourceLoader 这个接口,所有的上下文都可以用于获取 Resource 实例对象

  • 我们可以在特定的应用上下文中通过 getResource() 来获取特定类型的 Resource 实例,但是的保证 location 路径没有特殊的前缀,如 classpatch: 等,如果有特定前缀慢么会强制使用相应的资源类型,与上下文无关。

Prefix Example Explanation
classpath: classpath:com/myapp/config.xml 从类路径加载
file: file:///data/config.xml 从文件系统作为 URL 加载
http: https://myserver/logo.png 按照URL形式加载
(none) /data/config.xml 取决于应用上下文

ResourceLoader 的子类结构:

3.1 DefaultResourceLoader

这个类是 ResourceLoader 的默认实现类,与 Resource 接口的 AbstractResource 一样,

3.1.1. 构造函数
  • 提供有参和无参的构造函数,有参构造函数接受 ClassLoader 类型,如不带参数则使用默认的 ClassLoader , Thread.currentThread()#getContextClassLoader()

核心代码代码,部分省去:

public class DefaultResourceLoader implements ResourceLoader {

    @Nullable
    private ClassLoader classLoader;

    private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

    private final Map<Class<?>, Map<Resource, ?>> resourceCaches = new ConcurrentHashMap<>(4);

    /**
     * 无参构造函数
     * @see java.lang.Thread#getContextClassLoader()
     */
    public DefaultResourceLoader() {
        this.classLoader = ClassUtils.getDefaultClassLoader();
    }

    /**
     * 带ClassLoader的有参构造函数
     */
    public DefaultResourceLoader(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * 设置 ClassLoader
     */
    public void setClassLoader(@Nullable ClassLoader classLoader) {
        this.classLoader = classLoader;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值