Spring 资源管理 (Resource)

简介: Spring 为什么引入资源管理?Java 中有各种各样的资源,资源的位置包括本地文件系统、网络、类路径等,资源的形式可以包括文件、二进制流、字节流等,针对不同的资源又有不同的加载形式。

Spring 为什么引入资源管理?


Java 中有各种各样的资源,资源的位置包括本地文件系统、网络、类路径等,资源的形式可以包括文件、二进制流、字节流等,针对不同的资源又有不同的加载形式。本地文件系统中的文件在 Java 中使用 File 表示,使用 FileInputStream 读取。网络上的资源使用 URL 表示,使用 URLConnection 获取 InputStream 进行读取。而类路径下的资源使用 ClassLoader 进行读取。为了使用统一的方式访问资源,Spring 将资源抽象为 Resource,将资源的加载抽象为 ResourceLoader。Spring 配置文件的读取以及扫描包中的 bean 都会通过 Resource 访问资源。


资源抽象 Resource


Resource 是 Spring 对资源抽象的一个接口,具体的资源可以有不同的实现类。Resource 相关方法如下:


public interface Resource extends InputStreamSource {
  // 资源是否以物理的形式真实存在
  boolean exists();
  // 资源是否可以通过 #getInputStream() 方法进行读取
  default boolean isReadable() {
    return exists();
  }
  // 资源是否已经被打开
  default boolean isOpen() {
    return false;
  }
  // 资源是否为文件系统中的资源
  default boolean isFile() {
    return false;
  }
  // 获取资源 URL 的表示形式
  URL getURL() throws IOException;
  // 获取资源 URI 的表示形式
  URI getURI() throws IOException;
  // 获取资源文件的表示形式
  File getFile() throws IOException;
  // 获取资源 Channel 的表示形式
  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();
}


Resource 接口继承了接口 InputStreamSource ,InputStreamSource 源码如下:


public interface InputStreamSource {
  // 获取输入流
  InputStream getInputStream() throws IOException;
}


因此,每个 Resource 都可以获取到 InputStream。常见的 Resource 如下面的类图所示。


image.png


每个 Resource 的实现都封装了具体的资源。Resource 由 AbstractResource 进行主要的抽象实现,其子类可能根据封装的资源进行重写,由于源码比较简单,这里不再进行分析,感兴趣的朋友可以自行查看相关源码。 主要的 Resource 包括如下。


FileSystemResource:对文件系统中 File 及 Path 的封装,除了可以读取资源,还可以对资源进行写操作。


ClassPathResource:类路径下资源的封装。

UrlResource:URL 资源的封装。

InputStreamResource:输入流资源的封装。

ByteArrayResource:字节数组的封装。

ServletContextResource:对 Servlet 上下文的封装。

资源加载抽象 ResourceLoader

与 Java 中的类加载相似,Java 使用 ClassLoader 加载类,而 Spring 抽象出 ResourceLoader 加载 Resource。ResourceLoader 也是一个接口,根据不同的资源可以有不同的实现。ResourceLoader 源码如下:


public interface ResourceLoader {
  //类资源位置的前缀 classpath:
  String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
  // 根据指定的资源位置获取资源
  Resource getResource(String location);
  // 获取当前类加载器中使用的 ClassLoader
  @Nullable
  ClassLoader getClassLoader();
}


ResourceLoader 中定义了根据资源位置获取资源的方法,相关类图见下图。


image.png


DefaultResourceLoader 是 ResourceLoader 的默认实现,其根据资源路径的协议进行解析为不同的 Resource 实现,但是它只能够根据资源路径获取一个 Resource。其获取资源的方法源码如下。


  @Override
  public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");
    // 先根据保存的协议解析器解析支持协议的资源
    for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
      Resource resource = protocolResolver.resolve(location, this);
      if (resource != null) {
        return resource;
      }
    }
    // 使用 Class 或 ClassLoader 获取资源
    if (location.startsWith("/")) {
      return getResourceByPath(location);
    }
    else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
      // 获取类路径下的资源
      return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
    }
    else {
      try {
        // 尝试获取 URL 资源
        // 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) {
        // No URL -> resolve as resource path.
        return getResourceByPath(location);
      }
    }
  }


DefaultResourceLoader 先根据协议解析器获取资源,因此我们可以定义自己的协议解析器解析自定义的协议的资源。如果路径以 / 开头,它会获取到一个 ClassPathContextResource 资源,否则如果以资源位置以 classpath: 开头,会获取到一个 ClassPathResource 资源,最后会尝试获取 UrlResource 资源。


如果想要根据资源路径的模式字符串获取多个 Resource ,则只能通过 ResourcePatternResolver,ResourcePatternResolver 源码如下。


public interface ResourcePatternResolver extends ResourceLoader {
  // 类路径下资源文件的前缀
  String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
  // 根据资源路径模式字符串获取资源
  Resource[] getResources(String locationPattern) throws IOException;
}


ResourcePatternResolver 只有一个实现 PathMatchingResourcePatternResolver,它会根据 ant 风格的路径去查找资源。实现源码如下。


  // ant 风格的路径匹配
  private PathMatcher pathMatcher = new AntPathMatcher();
  @Override
  public Resource[] getResources(String locationPattern) throws IOException {
    Assert.notNull(locationPattern, "Location pattern must not be null");
    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
      // 处理 classpath*: 开头类路径下的资源
      // a class path resource (multiple resources for same name possible)
      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
        // 查询符合模式的资源
        // a class path resource pattern
        return findPathMatchingResources(locationPattern);
      }
      else {
        // 查询不匹配模式的资源
        // all class path resources with the given name
        return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
      }
    }
    else {
      // 处理非类路径下的资源
      // Generally only look for a pattern after a prefix here,
      // and on Tomcat only after the "*/" separator for its "war:" protocol.
      int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
          locationPattern.indexOf(':') + 1);
      if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
        // 查找匹配模式的资源
        // a file pattern
        return findPathMatchingResources(locationPattern);
      }
      else {
        // 查询不匹配模式的资源
        // a single resource with the given name
        return new Resource[] {getResourceLoader().getResource(locationPattern)};
      }
    }
  }


获取资源时会先判断资源路径是否为类路径,然后再判断路径是否为支持的模式,默认支持 ant 风格的路径匹配,对类路径下的资源和非类路径下的资源具有不同的处理。


如何在 Spring 中获取 Resource 和 ResourceLoader


Spring 的内部有关资源的加载大量使用了 Resource 和 ResourceLoader,自然我们也同样可以使用 Resource 获取资源。

由于 Resource 与具体的资源进行绑定,Spring 并未把它作为 bean 注入到容器中,为了获取 Resource ,我们可以通过在 bean 的 成员变量中通过 @Value 注入 Resource 及其数组对象。示例如下。


// 类路径下创建文件 META-INF/dev.properties 内容为 profile=dev
// 类路径下创建文件 META-INF/prod.properties 内容为 profile=prod
public class Main {
    @Value("classpath:/META-INF/prod.properties")
    private Resource resource;
    @Value("classpath*:/META-INF/*.properties")
    private Resource[] resources;
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        Main bean = context.getBean(Main.class);
        String content = FileReader.create(bean.resource.getFile()).readString();
        System.out.println(content);
        System.out.println("=========");
        for (Resource resource : bean.resources) {
            System.out.println(FileReader.create(resource.getFile()).readString());
            System.out.println("=========");
        }
    }
}


执行结果如下:


profile=prod
=========
profile=dev
=========
profile=prod
=========


通过 @Value 注入 Resource ,成功读取到了类路径下的资源文件。


ResourceLoader 作为可能会被经常使用的组件,Spring 已经将其注册为 bean,因此可以直接通过 @Autowire 注入,另外由于 ApplicationContext 继承了 ResourceLoader 接口,因此也可以直接通过 @Autowire 注入 ApplicationContext 来使用 ResourceLoader,此外 Spring 还提供了 ResourceLoaderAware 接口,在 bean 的生命周期中,如果 bean 实现了接口 ResourceLoaderAware ,则 Spring 会调用 setResourceLoader 方法,这样就拿到了 ResourceLoader,拿到后我们就可以直接用来加载资源。示例代码如下。


public class Main implements ResourceLoaderAware {
    @Autowired
    private ResourceLoader resourceLoader;
    @Autowired
    private ApplicationContext applicationContext;
    private ResourceLoader awareResourceLoader;
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.awareResourceLoader = resourceLoader;
    }
    public static void main(String[] args) throws IOException {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        Main bean = context.getBean(Main.class);
        System.out.println(bean.resourceLoader);
        System.out.println(bean.applicationContext);
        System.out.println(bean.awareResourceLoader);
    }
}


执行结果如下。


org.springframework.context.annotation.AnnotationConfigApplicationContext@6aaa5eb0, started on Wed Sep 02 22:36:40 CST 2020
org.springframework.context.annotation.AnnotationConfigApplicationContext@6aaa5eb0, started on Wed Sep 02 22:36:40 CST 2020
org.springframework.context.annotation.AnnotationConfigApplicationContext@6aaa5eb0, started on Wed Sep 02 22:36:40 CST 2020


三种方式都打印出来了结果,说明这三种方式都可以正常获取 ResourceLoader,并且这三种方式获取到的对象为同一个。


总结

Resource 和 ResourceLoader 作为 Spring 中资源和加载资源的抽象,在底层加载资源的地方都会被用到,通过对这两者的熟悉,在阅读 Spring 源码时,可以把精力放在其他地方,并且我们也可以使用 Resource 获取我们自己的资源。



目录
相关文章
|
6月前
|
Java 数据库 Spring
【Spring】资源操作管理:Resource、ResourceLoader、ResourceLoaderAware;
【Spring】资源操作管理:Resource、ResourceLoader、ResourceLoaderAware;
133 1
|
6月前
|
Java Spring 容器
Spring中@Autowired和@Resource注解异同点
Spring中@Autowired和@Resource注解异同点
71 0
|
3月前
|
存储 Kubernetes 监控
在K8S中,Resource Quotas是什么?如何做资源管理的?
在K8S中,Resource Quotas是什么?如何做资源管理的?
|
6月前
|
算法 程序员 数据库连接
深入探索C++中的RAII原则:资源管理的艺术 (In-Depth Exploration of RAII in C++: The Art of Resource Management)...
深入探索C++中的RAII原则:资源管理的艺术 (In-Depth Exploration of RAII in C++: The Art of Resource Management)...
218 2
|
6月前
|
安全 Java 开发者
Spring依赖注入大揭秘:@Autowired、@Qualifier和@Resource的区别与应用
Spring依赖注入大揭秘:@Autowired、@Qualifier和@Resource的区别与应用
316 0
|
6月前
|
Java Spring
spring注解@Autowired、@Resource说明
spring注解@Autowired、@Resource说明
|
6月前
|
Java 编译器 Spring
Spring中@Autowired和@Resource的区别
Spring中@Autowired和@Resource的区别
141 0
|
6月前
|
Java Spring 容器
[Spring] 字节一面~Spring 如何解决循环依赖问题 以及 @resource 与 @autowire 同时存在时谁生效
[Spring] 字节一面~Spring 如何解决循环依赖问题 以及 @resource 与 @autowire 同时存在时谁生效
189 0
|
6月前
|
Java Spring 容器
Spring自动装配【Bean的作用域、@Autowried、@Resource】
Spring自动装配【Bean的作用域、@Autowried、@Resource】
|
存储 Java Spring
Spring框架中的Resource接口是什么,以及它在加载和访问资源时的关键作用
使用 Resource 加载资源 要使用 Resource 接口加载资源,首先需要获取一个 ResourceLoader 实例,通常可以通过依赖注入来获得。接下来,您可以使用 ResourceLoader 来获取 Resource 对象,然后使用它来访问资源的内容。
136 0

热门文章

最新文章