文档章节

Spring源码解析:资源的描述与加载

zhenchao
 zhenchao
发布于 2017/06/04 15:20
字数 3339
阅读 126
收藏 0
点赞 0
评论 0

spring version : 4.3.x

资源(File、URL、Classpath 等等)是众多框架使用和运行的基础,Spring 当然也不例外,在具体探究 Spring 容器的设计实现之前,我们先来做一些准备工作,对于容器运行基础的资源描述做一个简单的了解。

一. 资源的抽象声明

在 java 中,资源被抽象成 URL,通过注册相应的 handler 来处理不同资源的操作逻辑,而 Spring 则采用 Resource 来对各种资源进行统一抽象,org.springframework.core.io.Resource 是一个接口,定义了资源的基本操作,包括是否存在、是否可读、是否已经打开等等。

Resource 接口声明如下:

public interface InputStreamSource {
    /** 返回一个新的输入流 */
    InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
    /** 资源是否存在 */
    boolean exists();
    /** 资源是否可读 */
    boolean isReadable();
    /** 资源流是否打开 */
    boolean isOpen();
    /** 返回资源的URL */
    URL getURL() throws IOException;
    /** 返回资源的URI */
    URI getURI() throws IOException;
    /** 返回资源的File对象 */
    File getFile() throws IOException;
    /** 返回文件的长度 */
    long contentLength() throws IOException;
    /** 返回文件上次被修改的时间戳 */
    long lastModified() throws IOException;
    /** 依据当前资源创建一个相对的资源,并返回资源对象 */
    Resource createRelative(String relativePath) throws IOException;
    /** 返回资源的文件名 */
    String getFilename();
    /** 返回资源的描述信息 */
    String getDescription();
}

由继承关系可以看到 Resource 继承了 org.springframework.core.io.InputStreamSource,InputStreamSource 封装任何可以返回 InputStream 的类,并仅声明了一个方法:getInputStream(),用于返回一个新的 InputStream 对象。

Resource 本身则抽象了资源的基本操作,Spring 也针对不同的资源定义了相应的类实现,比如:文件(FileSystemResource);Byte数组资源(ByteArrayResource);ClassPath资源(ClassPathResource);URL资源(UrlResource)等等,具体如下图:

spring-resource

二. 资源的具体定义

下面来分别看看每一种 resource 的定义,我们按照层次由上到下一层层来看:

2.1 第一层

这一层主要涉及如下 5 个类或接口:

  1. org.springframework.web.servlet.resource.EncodedResource
  2. org.springframework.web.servlet.resource.VersionedResource
  3. org.springframework.core.io.WritableResource
  4. org.springframework.core.io.AbstractResource
  5. org.springframework.core.io.ContextResource

这些资源接口,有些已经派生出了多个类,有些则还没有具体的实现,留给以后的版本,以及用户去自定义扩展。

  • org.springframework.web.servlet.resource.EncodedResource

EncodedResource 在 4.1 版本中引入,正如其名,是与编码相关的资源定义,该接口仅声明了一个方法 getContentEncoding(),用来获取资源内容编码,编码形式可以参考 HTTP/1.1 对 Content-Encoding 首部的定义。

Content-Encoding

该首部用于说明是否对某对象进行了编码,并告知客户端对对象进行了哪种或哪些类型的编码,客户端可以据此进行适当的解码操作。

  • 类型:实体首部
  • 基本语法:Content-Encoding: 1# content-coding

上面是对 HTTP/1.1 中的 Content-Encoding 首部的简单介绍。

  • org.springframework.web.servlet.resource.VersionedResource

VersionedResource 在 4.2.5 版本引入,用于对资源的版本进行描述,该接口仅声明了一个方法 getVersion(),用于获取版本信息。

  • org.springframework.core.io.WritableResource

WritableResource 接口用于描述一个资源是否支持可写的特性,在基本资源接口定义 Resource 中,仅描述了一个资源是否可读,因为可读相对于可写是更加基本的特性,而对于可读又可写的文件来说,WritableResource 接口进一步完善描述了其资源特性。该接口声明了两个方法 isWritable() 和 getOutputStream(),前者用于返回文件是否可写,后者则返回可写文件的 OutputStream。

  • org.springframework.core.io.AbstractResource

AbstractResource 不是对某一特性的描述,而是一种编程技巧,Resource 中声明了资源的多种操作,如果我们直接去实现 Resource 接口,势必要提供针对每一个方法的实现,而这些方法并不是全部都需要提供支持,所以 AbstractResource 对所有方法提供了默认实现,通过继承 AbstractResource,我们可以针对性的选择实现我们需要的方法。

  • org.springframework.core.io.ContextResource

ContextResource 是在 2.5 版本引入的一个扩展接口,用于描述从上下文环境中加载的资源,该接口仅声明了一个方法 getPathWithinContext(),用于获取上下文环境的相对路径。

2.2 第二层

第二层主要派生自 AbstractResource,包括:

  1. org.springframework.core.io.FileSystemResource
  2. org.springframework.core.io.PathResource
  3. org.springframework.core.io.ByteArrayResource
  4. org.springframework.core.io.VfsResource
  5. org.springframework.core.io.InputStreamResource
  6. org.springframework.core.io.DescriptiveResource
  7. org.springframework.beans.factory.support.BeanDefinitionResource
  8. org.springframework.core.io.AbstractFileResolvingResource

这些资源类全部继承自 AbstractResource,并依据自己的特性,对 Resource 中声明的方法做了选择性的实现。

  • org.springframework.core.io.FileSystemResource

FileSystemResource 是对文件系统类型资源的描述,这也是 Spring 中典型的资源类型(这个类由 Spring 共同创始人 Juergen Hoeller 写于2003年12月28号),该类继承自 AbstractResource,并实现了 WritableResource 接口。

FileSystemResource 提供了两个构造方法分别由 java.io.File 对象和文件路径来构造资源对象,对于传入的路径 path,由于输入的不确定性,会利用 StringUtils.cleanPath(path) 对其进行格式化。FileSystemResource 实现的资源接口如下:

public class FileSystemResource extends AbstractResource implements WritableResource {
    /** 文件对象 */
    private final File file;
    /** 文件路径 */
    private final String path;

    // 构造函数省略

    public final String getPath();
    public boolean exists();
    public boolean isReadable();
    public InputStream getInputStream() throws IOException;
    public boolean isWritable();
    public OutputStream getOutputStream() throws IOException;
    public URL getURL() throws IOException;
    public URI getURI() throws IOException;
    public File getFile();
    public long contentLength() throws IOException;
    public Resource createRelative(String relativePath);
    public String getFilename();
    public String getDescription();
}

FileSystemResource 中的函数实现几乎都依赖于 java.io.File 的方法。这里提一下 createRelative(String relativePath),该方法基于当前路径创建相对资源,方法源码如下:

public Resource createRelative(String relativePath) {
    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
    return new FileSystemResource(pathToUse);
}

方法先利用 StringUtils.applyRelativePath(this.path, relativePath) 方法来创建资源绝对路径,主要操作是截取 path 的最后一个文件分隔符 ‘/’ 前面的内容与 relativePath 拼接,然后基于新的路径构造资源对象。

org.springframework.core.io.PathResource

PathResource 在 4.0 版本引入,是基于 jdk1.7 NIO 2.0 中的类 java.nio.file.Path 所实现的资源类型。NIO 2.0 针对本地 I/O 引入了许多新的类(如下所示),用来改变 java 在 I/O 方面一直被人诟病的慢特性。

import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;

所以 PathResource 也表示 Spring 由 BIO 向 NIO 的迈进。

  • org.springframework.core.io.ByteArrayResource

ByteArrayResource 利用字节数组作为资源存储的标的,jdk 原生也提供了字节数组式的 I/O 流,所以二者在设计思想是想通的。

  • org.springframework.core.io.VfsResource

VfsResource 对 JBoss Virtual File System (VFS) 提供了支持,针对 JBoss VFS 的说明,官网简介如下:

The Virtual File System (VFS) framework is an abstraction layer designed to simplify the programmatic access to file system resources. One of the key benefits of VFS is to hide certain file system details and allow for file system layouts that are not required to reflect a real file system. This allows for great flexibility and makes it possible to navigate arbitrary structures (ex. archives) as though they are part of a single file system.

具体没用过,不多做解释。

  • org.springframework.core.io.InputStreamResource

InputStreamResource 基于给定的 InputStream 来创建资源,流是一般文件的更低一层,程序设计的共性就是越往底层走需要考虑的问题就越多,所以 Spring 明确表示,如果有相应的上层实现则不推荐直接使用 InputStreamResource。

  • org.springframework.core.io.DescriptiveResource

DescriptiveResource 资源并不是一个真实可读的资源,而是对文件的一种描述,所以这类资源的 exists() 方法始终返回 false。这类资源的作用是在必要的时候用来占坑,比如文档所说的,当一个方法需要你传递一个资源对象,但又不会在方法中真正读取该对象的时候,如果没有合适的资源对象作为参数,就创建一个 DescriptiveResource 资源做参数吧。

  • org.springframework.beans.factory.support.BeanDefinitionResource

BeanDefinitionResource 是对 BeanDefinition 对象的一个包装,BeanDefinition 对象是 Spring 核心类之一,是对 bean 定义在内存中进行描述的数据结构,我们在配置文件中定义的 bean,经过加载之后都会以 BeanDefinition 对象的形式存储在容器中,具体加载过程后续会有专门文章讲解。

这里的 BeanDefinitionResource 仅仅是对 BeanDefinition 对象的持有,并提供返回的 get 方法,而一般资源操作方法几乎都不支持。

  • org.springframework.core.io.AbstractFileResolvingResource

AbstractFileResolvingResource 是解析 URL 所指代的文件为 java.io.File 对象的抽象资源类,具体的实现典型的有 UrlResource 和 ClassPathResource。AbstractFileResolvingResource的接口定义如下:

public abstract class AbstractFileResolvingResource extends AbstractResource {
    /** 解析 url 获取 File 对象 */
    public File getFile() throws IOException;
    /** 解析指代底层文件的 url 为 File 对象,比如压缩包中的文件 */
    protected File getFileForLastModifiedCheck() throws IOException;
    /** 解析 URI 所指代的 File 对象 */
    protected File getFile(URI uri) throws IOException;
    public boolean exists()
    public boolean isReadable();
    public long contentLength() throws IOException;
    public long lastModified() throws IOException;
}

我们来看一下 getFile() 和 getFileForLastModifiedCheck() 函数的实现,getFile(URI uri) 类似于 getFile():

public File getFile() throws IOException {
    URL url = this.getURL(); // 获取 url
    if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
        // 如果是 JBoss VFS 文件
        return VfsResourceDelegate.getResource(url).getFile();
    }
    return ResourceUtils.getFile(url, getDescription());
}

getFile() 解析 url 为 File 对象,方法首先调用 getURL() 获取 url 对象,然后检查当前 url 是不是 JBoss VFS 文件,如果是则走 VFS 文件解析策略,否则就调用工具类 org.springframework.util.ResourceUtils 的 getFile(URL resourceUrl, String description) 进行解析,解析过程如下:

public static File getFile(URL resourceUrl, String description) throws FileNotFoundException {
    Assert.notNull(resourceUrl, "Resource URL must not be null");
    if (!URL_PROTOCOL_FILE.equals(resourceUrl.getProtocol())) {
        // url 不是 file 协议,说明不是指代文件的 url
        throw new FileNotFoundException(
                description + " cannot be resolved to absolute file path because it does not reside in the file system: " + resourceUrl);
    }
    try {
        // 由 url 对象 构造 File 对象返回
        return new File(toURI(resourceUrl).getSchemeSpecificPart());
    } catch (URISyntaxException ex) {
        // Fallback for URLs that are not valid URIs (should hardly ever happen).
        return new File(resourceUrl.getFile());
    }
}

getFileForLastModifiedCheck() 相对于 getFile() 提供了对压缩文件 url 路径的解析:

protected File getFileForLastModifiedCheck() throws IOException {
    URL url = this.getURL();
    if (ResourceUtils.isJarURL(url)) {
        // 如果 URL 的协议是 jar, zip, vfszip, wsjar 之一
        URL actualUrl = ResourceUtils.extractArchiveURL(url);  // 解析 url
        if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
            // 如果是 JBoss VFS 文件
            return VfsResourceDelegate.getResource(actualUrl).getFile();
        }
        return ResourceUtils.getFile(actualUrl, "Jar URL");
    } else {
        // 不是 jar url
        return this.getFile();
    }
}

方法首先获取 url 对象,然后判断是不是 jar url,如果不是就走之前的 getFile() 进行常规解析,如果是,即当前 url 的协议是jar, zip, vfszip, wsjar 之一,则首先解析 url 得到常规 url 对象,然后执行和 getFile() 相同的逻辑。

2.3 第三层

  1. org.springframework.web.servlet.resource.TransformedResource
  2. org.springframework.core.io.UrlResource
  3. org.springframework.core.io.ClassPathResource
  4. org.springframework.web.portlet.context.PortletContextResource
  5. org.springframework.web.context.support.ServletContextResource

第三层主要是对第二层对象的进一步实现,包括 ByteArrayResource、AbstractFileResolvingResource,以及 ContextResource。

  • org.springframework.web.servlet.resource.TransformedResource

TransformedResource 继承自 ByteArrayResource,在 4.1 版本引入,相对于 ByteArrayResource 增加了对文件名和上次修改时间戳的描述。

  • org.springframework.core.io.UrlResource

UrlResource 是对 AbstractFileResolvingResource 的实现,这里的 URL 是常规 URL 对象的一个子集,主要考虑的是 “file:” 协议。UrlResource 提供了多种构造方法:

public UrlResource(URI uri) throws MalformedURLException;
public UrlResource(URL url);
public UrlResource(String path) throws MalformedURLException;
public UrlResource(String protocol, String location) throws MalformedURLException;
public UrlResource(String protocol, String location, String fragment) throws MalformedURLException;
  • org.springframework.core.io.ClassPathResource

ClassPathResource 是比较常用的一种资源,继承自 AbstractFileResolvingResource,是对类上下文环境内资源的一种描述,基于 ClassLoader 或 Class 来定位加载资源。

整个类的核心在于如何依据 ClassLoader 或 Class 进行资源定位,这归功于 resolveURL() 方法,源码如下:

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

方法的实现还是比较简单的,主要是基于类加载器去检索资源路径。

  • org.springframework.web.portlet.context.PortletContextResource

PortletContextResource 继承自 AbstractFileResolvingResource,实现了 ContextResource 接口,提供了对 javax.portlet.PortletContext 的支持。

  • org.springframework.web.context.support.ServletContextResource

ServletContextResource 同样继承自 AbstractFileResolvingResource,实现了 ContextResource 接口,提供了对 javax.servlet.ServletContext 的支持。

三. 资源的加载

Spring 为资源定义了一套专门的资源加载接口(类继承关系见下图),ResourceLoader 接口是整个继承体系的基础,该接口声明了两个方法(如下所示),getResource 用于获取指定的地址的资源对象,getClassLoader 则返回当前 ResourceLoader 所使用的类加载器,一些时候我们可能需要基于该类加载器执行一些相对定位操作:

public interface ResourceLoader {

    Resource getResource(String location);

    ClassLoader getClassLoader();

}

我们可以将所有的接口分为加载器和解析器两大类,加载器的作用不言而喻,对于解析器而言,由前面的分析我们知道 Spring 定义了多种资源类型,Spring 支持对于特定资源类型的解析并加载返回对应的资源对象。

spring-reaource-loader

在日常使用过程中,我们通常都是以 Ant 风格来配置资源路径,这给我们的配置带来了极大的灵活性,而正是 PathMatchingResourcePatternResolver 为 Ant 风格的配置提供了支持,路径的解析本质上依赖于各种规则,Ant 风格也不例外,有兴趣的同学可以自己阅读一下 PathMatchingResourcePatternResolver 的路径解析过程。

系列文章

  1. Spring源码解析:获取源码
  2. Spring源码解析:资源的描述与加载
  3. Spring源码解析:IoC容器的基本结构设计
  4. Spring源码解析:简单容器中Bean的加载过程初探
  5. Spring源码解析:默认标签的解析过程
  6. Spring源码解析:自定义标签的解析过程
  7. Spring源码解析:Bean实例的创建与初始化
  8. Spring源码解析:高级容器的扩展内幕
  9. Spring源码解析:循环依赖的探测与处理

鉴于作者水平有限,文中不免有错误之处,欢迎大家批评指正~

同步更新站点:www.zhenchao.org

© 著作权归作者所有

共有 人打赏支持
zhenchao
粉丝 101
博文 28
码字总数 98844
作品 0
海淀
高级程序员
深入理解Spring源码(一)-IOC容器的定位,载入,注册

前言:Spring源码继承,嵌套层次非常多,读起来非常容易晕,小伙伴们在看文章的时候一定要跟着文章的思路自己去源码里点一点,看一看,并且多看几次。就会越来越清晰。下面开始正题 1.Spring...

Meet相识_bfa5 ⋅ 05/01 ⋅ 0

Spring源码解析系列之IOC容器(一)

前言 实际上我所有的博客都是原来对原来印象笔记里笔记内容的加工,关于Spring源码自己已经解析了很多遍,但是时间长总是忘记,写一篇博客权当加强记忆,也算再次学习下大师们的设计思想,思...

后厂村老司机 ⋅ 06/02 ⋅ 0

Spring IOC 实现原理

Spring IOC 实现原理 IOC: Inversion of Control ,即 "控制反转" , 不是什么技术,而是一种思想。原先需要自行实例化的对象, 交给IOC容器去实现。那么控制反转,谁被控制? 谁被反转 ? 在...

起个名忒难 ⋅ 05/17 ⋅ 0

spring boot框架学习2-spring boot核心(1)

本节主要: 1:解析spring boot入口和@SpringBootApplication源码详解 SpringBootApplication包含: @SpringBootConfiguration @ComponentScan @EnableAutoConfiguration 本文是《凯哥陪你学......

799879287 ⋅ 2017/10/26 ⋅ 0

spring源码解析上下文初始化ContextLoaderListener

前言 从本篇文章开始主要介绍spring源码解析相关的spring上下文初始化、bean定义解析、beanFactory创建、初始化、bean定义注册到beanFactory、bean实例化、依赖注入流程中相关的步骤,由于s...

天河2018 ⋅ 05/10 ⋅ 0

spring源码解析系列之事件驱动模型@EventListener

说到前面 本次主要介绍监听器的实例化过程源码解析。 正文 我们跟踪@EventListener源码可以发现,背后的实现是这个类EventListenerMethodProcessor,spring应用上下文启动的时候会加载这个类...

天河2018 ⋅ 04/27 ⋅ 0

Spring Boot干货系列: (四)开发Web应用之Thymeleaf篇

        前言   Web开发是我们平时开发中至关重要的,这里就来介绍一下Spring Boot对Web开发的支持。   正文   Spring Boot提供了spring-boot-starter-web为Web开发予以支持,s...

后端编程嘟 ⋅ 2017/03/13 ⋅ 0

Spring Boot国际化开发实战

本章将讲解如何在Spring Boot和Thymeleaf中做页面模板国际化的支持,根据系统语言环境或者session中的语言来自动读取不同环境中的文字。 国际化自动配置 Spring Boot中已经对国际化这一块做了...

Java技术栈 ⋅ 06/08 ⋅ 0

Spring核心源码:ApplicationContext

废话 spring版本:4.0.6 随便做了这么多年的spring,但是源码就只阅读过 shiro的。一直想去读一下spring,mybatis,netty,这些结构优美的开源框架的源码。 核心包: spring-context:spring的上...

GITTODO ⋅ 04/25 ⋅ 0

SpringMVC总结篇

SpringMVC是基于MVC设计理念的一款优秀的Web框架,是目前最流行的MVC框架之一,SpringMVC通过一套注解,让POPJ成为处理请求的控制器,而无需实现任何接口,然后使用实现接口的控制器也完全没...

qq58ef2349113e9 ⋅ 2017/04/13 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

个人博客的运营模式能否学习TMALL天猫质量为上?

心情随笔|个人博客的运营模式能否学习TMALL天猫质量为上? 中国的互联网已经发展了很多年了,记得在十年前,个人博客十分流行,大量的人都在写博客,而且质量还不错,很多高质量的文章都是在...

原创小博客 ⋅ 今天 ⋅ 0

JavaScript零基础入门——(十一)JavaScript的DOM操作

JavaScript零基础入门——(十一)JavaScript的DOM操作 大家好,欢迎回到我们的JavaScript零基础入门。最近有些同学问我说,我讲的的比书上的精简不少。其实呢,我主要讲的是我在开发中经常会...

JandenMa ⋅ 今天 ⋅ 0

volatile和synchronized的区别

volatile和synchronized的区别 在讲这个之前需要先了解下JMM(Java memory Model :java内存模型):并发过程中如何处理可见性、原子性、有序性的问题--建立JMM模型 详情请看:https://baike.b...

MarinJ_Shao ⋅ 今天 ⋅ 0

深入分析Kubernetes Critical Pod(一)

Author: xidianwangtao@gmail.com 摘要:大家在部署Kubernetes集群AddOn组件的时候,经常会看到Annotation scheduler.alpha.kubernetes.io/critical-pod"="",以表示这是一个关键服务,那你知...

WaltonWang ⋅ 今天 ⋅ 0

原子性 - synchronized关键词

原子性概念 原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。 原子性的实现方式 在jdk中,原子性的实现方式主要分为: synchronized:关键词,它依赖于JVM,保证了同...

dotleo ⋅ 今天 ⋅ 0

【2018.06.22学习笔记】【linux高级知识 14.4-15.3】

14.4 exportfs命令 14.5 NFS客户端问题 15.1 FTP介绍 15.2/15.3 使用vsftpd搭建ftp

lgsxp ⋅ 今天 ⋅ 0

JeeSite 4.0 功能权限管理基础(Shiro)

Shiro是Apache的一个开源框架,是一个权限管理的框架,实现用户认证、用户授权等。 只要有用户参与一般都要有权限管理,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户...

ThinkGem ⋅ 昨天 ⋅ 0

python f-string 字符串格式化

主要内容 从Python 3.6开始,f-string是格式化字符串的一种很好的新方法。与其他格式化方式相比,它们不仅更易读,更简洁,不易出错,而且速度更快! 在本文的最后,您将了解如何以及为什么今...

阿豪boy ⋅ 昨天 ⋅ 0

Python实现自动登录站点

如果我们想要实现自动登录,那么我们就需要能够驱动浏览器(比如谷歌浏览器)来实现操作,ChromeDriver 刚好能够帮助我们这一点(非谷歌浏览器的驱动有所不同)。 一、确认软件版本 首先我们...

blackfoxya ⋅ 昨天 ⋅ 0

线性回归原理和实现基本认识

一:介绍 定义:线性回归在假设特证满足线性关系,根据给定的训练数据训练一个模型,并用此模型进行预测。为了了解这个定义,我们先举个简单的例子;我们假设一个线性方程 Y=2x+1, x变量为商...

wangxuwei ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部