余瑜的博客 余瑜的博客
首页
  • 并发
  • 线程池
  • spring
  • maven
  • 其他
  • redis
  • mysql
  • linux
  • zookeeper
  • docker
  • terminal
  • kong插件开发
  • 资料
  • leetCode-简单
  • blog
  • 其他
关于
GitHub (opens new window)
首页
  • 并发
  • 线程池
  • spring
  • maven
  • 其他
  • redis
  • mysql
  • linux
  • zookeeper
  • docker
  • terminal
  • kong插件开发
  • 资料
  • leetCode-简单
  • blog
  • 其他
关于
GitHub (opens new window)
  • 并发

  • 线程池

  • spring

    • Spring 统一资源加载策略
      • Resource
      • ResourceLoader
        • DefaultResourceLoader.getResource
        • ProtocolResolver
      • 总结
    • BeanDefinition加载、解析、处理、注册
    • BeanFactory1-DefaultSingletonBeanRegistry
    • spring-boot-data-elasticsearch
    • springboot配置Druid监控
    • springboot项目初始化时读取数据库
    • 文件上传
    • SpringBoot优雅停机的正确姿势.md
    • spring源码阅读神器
  • maven

  • 其他

  • JAVA
  • spring
余瑜
2019-07-24
目录

Spring 统一资源加载策略

# Resource

image.png

  • FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。
  • ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。
  • UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。
  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。
  • InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。

#

# ResourceLoader

Resource 定义了统一的资源,那资源的加载则由 ResourceLoader 来统一定义。

public interface ResourceLoader {

    String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX; // CLASSPATH URL 前缀。默认为:"classpath:"

    Resource getResource(String location);

    ClassLoader getClassLoader();

}
1
2
3
4
5
6
7
8
9

#getResource(String location) 方法,根据所提供资源的路径 location 返回 Resource 实例,但是它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断。

  • 该方法支持以下模式的资源加载:
    • URL位置资源,如 "file:C:/test.dat" 。
    • ClassPath位置资源,如 "classpath:test.dat 。
    • 相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同。
  • 该方法的主要实现是在其子类 DefaultResourceLoader

# DefaultResourceLoader.getResource

@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");

    // 首先,通过 ProtocolResolver 来加载资源
    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    // 其次,以 / 开头,返回 ClassPathContextResource 类型的资源
    if (location.startsWith("/")) {
        return getResourceByPath(location);
    // 再次,以 classpath: 开头,返回 ClassPathResource 类型的资源
    } else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
        return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    // 然后,根据是否为文件 URL ,是则返回 FileUrlResource 类型的资源,否则返回 UrlResource 类型的资源
    } else {
        try {
            // Try to parse the location as a URL...
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        } catch (MalformedURLException ex) {
            // 最后,返回 ClassPathContextResource 类型的资源
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  • 首先,通过 ProtocolResolver 来加载资源,成功返回 Resource 。
  • 其次,若 location 以 "/" 开头,则调用 #getResourceByPath() 方法,构造 ClassPathContextResource 类型资源并返回。
  • 再次,若 location 以 "classpath:" 开头,则构造 ClassPathResource 类型资源并返回。在构造该资源时,通过 #getClassLoader() 获取当前的 ClassLoader。
  • 然后,构造 URL ,尝试通过它进行资源定位,若没有抛出 MalformedURLException 异常,则判断是否为 FileURL , 如果是则构造 FileUrlResource 类型的资源,否则构造 UrlResource 类型的资源。
  • 最后,若在加载过程中抛出 MalformedURLException 异常,则委派 #getResourceByPath() 方法,实现资源定位加载。

# ProtocolResolver

org.springframework.core.io.ProtocolResolver ,用户自定义协议资源解决策略,作为 DefaultResourceLoader 的 SPI:它允许用户自定义资源加载协议,而不需要继承 ResourceLoader 的子类。
如果要实现自定义 Resource,我们只需要继承 AbstractResource 即可,但是有了 ProtocolResolver 后,我们不需要直接继承 DefaultResourceLoader,改为实现 ProtocolResolver 接口也可以实现自定义的 ResourceLoader。
调用方式:

/**
 * ProtocolResolver 集合
 */
private final Set<ProtocolResolver> protocolResolvers = new LinkedHashSet<>(4);

public void addProtocolResolver(ProtocolResolver resolver) {
    Assert.notNull(resolver, "ProtocolResolver must not be null");
    this.protocolResolvers.add(resolver);
}
1
2
3
4
5
6
7
8
9

# 总结

  1. ResourceLoader和Resource主要用到了策略模式, ResourceLoader依据不同location开头来定位使用哪一个Resource实现类.
  2. 其中感觉最妙的是在ResourceLoader中添加了Set<ProtocolResolver>,在获取资源时先进行遍历,从而使用户在自定义资源加载器时不需要继承ResourceLoader的子类
  3. 这个位置使用set集合是因为可以设置多个Resource策略, 传入的_location在集合中_可以获取一个对应的Reource,多次解析location以获取集合中对应的索引Resource
上次更新: 2021/02/20, 20:09:17

← ExecutorCompletionService源码 BeanDefinition加载、解析、处理、注册→

Theme by Vdoing | Copyright © 2018-2022 逆光世间 | 备案号: 京ICP备19016086号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式