AI智能
改变未来

Spring系列18:Resource接口及内置实现


本文内容

  1. Resource

    接口的定义

  2. Resource

    接口的内置实现

  3. ResourceLoader

    接口

  4. ResourceLoaderAware

    接口

Resource接口的定义

Java 的标准

java.net.URL

类和各种

URL

前缀的标准处理程序不足以满足所有对低级资源的访问。 例如没有标准化的

URL

实现可用于访问需要从类路径或相对于

ServletContext

获取的资源。 虽然可以为专门的 URL 前缀注册新的处理程序(类似于现有的前缀处理程序如

http:

),但这通常相当复杂,并且 URL 接口仍然缺乏一些理想的功能,例如检查是否存在的方法 指向的资源。

针对上述这种情况,Spring 提供了更强大的接口

Resource

用于对低级资源的抽象访问,其定义和主要接口方法说明如下。

package org.springframework.core.io;public interface Resource extends InputStreamSource {// 特定资源是否存在boolean exists();// 内容非空可通过#getInputStream()读取default boolean isReadable() {return exists();}// 资源对应的InputStream是否已经打开,不能多次读取,应读取后关闭避免资源泄漏default boolean isOpen() {return false;}// 是否是 File 类型,配合 #getFile()default boolean isFile() {return false;}// 获取 URLURL getURL() throws IOException;// 获取URIURI getURI() throws IOException;// 获取 FileFile getFile() throws IOException;// 资源内容长度long contentLength() throws IOException;// 上次修改的时间戳long lastModified() throws IOException;// 给定路径创建资源Resource createRelative(String relativePath) throws IOException;// 获取文件名非全路径@NullableString getFilename();// 获取资源描述String getDescription();}

Resource

接口继承了

InputStreamSource

,其定义如下。

package org.springframework.core.io;public interface InputStreamSource {// 获取资源对应的 InputStreamInputStream getInputStream() throws IOException;}

Resource

接口并不是用于完全取代

java.net.URL

,而是尽可能地通过其实现类来包装

URL

进行处理,如

UrlResource

包装一个

URL

并使用包装的

URL

来完成它的的功能。具体看下一节的内置实现。

Resource接口的内置实现

  • UrlResource

  • ClassPathResource

  • FileSystemResource

  • ServletContextResource

  • InputStreamResource

  • ByteArrayResource

UrlResource

UrlResource

包装了

java.net.URL

,可用于访问通常可通过

URL

访问的任何对象,例如文件、HTTP 目标、FTP 目标等。这些资源通过标准前缀来区分,

file:

用于访问文件系统路径,

http:

用于通过 HTTP 协议访问资源,

ftp:

用于通过 FTP 访问资源等。

ClassPathResource

ClassPathResource

表示应从类路径获取的资源。它使用线程上下文类加载器、给定的类加载器或给定的类来加载资源。可通过特定前缀

classpath:

指定为该资源。

如果类路径资源驻留在文件系统中,则此

Resource

实现支持解析为

java.io.File

,但不支持在 jar 包中且尚未解压的类路径资源。

FileSystemResource

这是

java.io.File

和

java.nio.file.Path

句柄的资源实现。它支持作为

File

和

URL

的解析。

ServletContextResource

这是

ServletContext

资源的

Resource

实现,它解释相关 Web 应用程序根目录中的相对路径。

它始终支持流访问和

URL

访问,但仅在扩展 Web 应用程序存档并且资源物理位于文件系统上时才允许

java.io.File

访问。它是否被解压并在文件系统上或直接从 JAR 或其他地方(如数据库)访问实际上取决于 Servlet 容器。

ByteArrayResource

给定字节数组的资源实现。它为给定的字节数组创建一个

ByteArrayInputStream

。对于从任何给定的字节数组加载内容很有用,而不必求助于单一使用的

InputStreamResource

。

InputStreamResource

InputStreamResource

是给定

InputStream

的资源实现。首选

ByteArrayResource

或任何基于文件的

Resource

实现,仅当没有特定的

Resource

实现适用时才应使用它。

与其他 Resource 实现相比,这是一个已打开资源的描述符,接口

isOpen()

返回

true

。如果需要将资源描述符保存在某处或需要多次读取流,请不要使用它。

ResourceLoader

接口

ResourceLoader 用于加载资源(例如类路径或文件系统资源)的策略接口。

package org.springframework.core.io;public interface ResourceLoader {/** Pseudo URL prefix for loading from the class path: "classpath:". */String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;// 返回指定资源位置的资源句柄Resource getResource(String location);// 获取 ClassLoader// 需要直接访问 ClassLoader 的客户端可以通过 ResourceLoader 统一的方式进行访问,而不是依赖于线程上		下文 ClassLoader。@NullableClassLoader getClassLoader();}

源码中对于的说明:

  • 句柄应始终是可重用的资源描述符,允许多个 Resource.getInputStream() 调用。
  • 必须支持完全限定的 URL,例如“
    file:C:/test.dat

    ”。

  • 必须支持类路径伪 URL,例如“
    classpath:test.dat

    ”。

  • 应该支持相对文件路径,例如“
    WEB-INF/test.dat

    ”。 (这将是特定于实现的,通常由

    ApplicationContext

    实现提供。)

  • 资源句柄并不意味着现有资源;需要调用
    Resource.exists()

    来检查是否存在。

所有

org.springframework.context.ApplicationContext

都必须实现该

ResourceLoader

接口。调用

getResource()

接口的返回值类型取决于当前context的类型,当前context会自动转换。如下案例。

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

如

ctx

是

ClassPathXmlApplicationContext

返回

ClassPathResource

;

如

ctx

是

FileSystemXmlApplicationContext

返回

FileSystemResource

;

如

ctx

是WebApplicationContext返回
ServletContextResource

如果不想依赖

context

类型决定返回资源的类型,可以指定前缀的方式,强制返回特定类型的资源。如下案例。

Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");// 返回 ClassPathResource
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");// 返回 UrlResource

指定前缀对于资源加载方式和返回的影响对应关系如下表。

| 前缀 | 例子 | 如何解析 || :——— | :———————————————————– | :——————————– || classpath: |

classpath:com/myapp/config.xml

| 从类路径加载 || file: |

file:///data/config.xml | 从文件系统作为

URL

加载 | | | http: |

Java开发/logo.png` | 从文件系统作为

URL

加载 || (none) |

/data/config.xml

| 取决于底层的

ApplicationContext

|

ResourceLoaderAware

接口

ResourceLoaderAware

接口是一个特殊的回调接口,它标识期望提供

ResourceLoader

引用的组件。其定义如下。

package org.springframework.context;public interface ResourceLoaderAware extends Aware {// 可自定义保存一个ResourceLoader的引用来进行资源加载void setResourceLoader(ResourceLoader resourceLoader);}

由于

org.springframework.context.ApplicationContext

实现了

ResourceLoader

,因此 bean 还可以实现

ApplicationContextAware

接口并直接使用提供的应用程序上下文来加载资源。从面向接口编程和解耦的角度来说,需要

ResourceLoader

的来进行资源加载,更推荐实现“ResourceLoaderAware


接口。

深一层思考,如果容器中的一个bean需要一个

ResourceLoader

依赖用于加载资源,除了实现

ResourceLoaderAware

,是否还有其它方式呢?

ResourceLoader

实例会自动注入到IoC容器,我们可通过构造函数、setter方法、@Autowire注解等方式,直接注入该依赖,具体看一看之前Ioc相关的文章。

配置文件当做

Resource

注入到bean依赖中

如果某个bean依赖于特定的

Resource

,那么我们如何快速优雅地注入该依赖呢?直接上代码。

public class MyConfig {/*** 通过配置文件注入的资源*/private Resource template;public Resource getTemplate() {return template;}public void setTemplate(Resource template) {this.template = templa}}

MyConfig

依赖一个类文件路径下的一个配置文件。

对应的xml文件

spring.xml

和配置文

db.properties

件如下:

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig"><property name="template" value="db.properties"></property></bean></beans>
url=jdbc:mysql://localhost/testusername=rootpassword=456max=1024

运行主程序如下:

package com.crab.spring.ioc.demo02;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.core.io.ClassPathResource;import org.springframework.core.io.Resource;public class ResourceTest {public static void main(String[] args) {ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring" +".xml");MyConfig myConfig = context.getBean(MyConfig.class);Resource template = myConfig.getTemplate();System.out.println(template);System.out.println(template instanceof  ClassPathResource);}}

运行结果:

class path resource [db.properties]trueclass path resource [db.properties]
  • 成功注入了
    Resourece

    资源到

    MyConfig

    的bean中

  • 注入
    Resource

    实际是我们通过

    ClassPathXmlApplicationContext

    加载的

    ClassPathResource

扩展:结合上一篇的资源特定前缀和

ApplicationContext

的关系,忘了请翻看下,我们也可以指定前缀来加载特定的资源。较为简单就不演示了。

<bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig"><property name="template" value="classpath:db.properties"></property></bean>file:///some/resource/path
<bean id="myConfig" class="com.crab.spring.ioc.demo02.MyConfig"><property name="template" value="file:///some/resource/path"></property></bean>// 注入的是FileSystemResource

使用资源路径配置应用上下文

回顾一下,

ResourceLoader

章节,我们分析了指定资源路径前缀对于资源加载方式的影响,对应关系如下表。下面将分带前缀和不带前缀的方式来配置应用上下文。

| 前缀 | 例子 | 如何解析 || :——— | :———————————————————– | :——————————– || classpath: |

classpath:com/myapp/config.xml

| 从类路径加载 || file: |

file:///data/config.xml | 从文件系统作为

URL

加载 | | | http: |

Java开发/logo.png` | 从文件系统作为

URL

加载 || (none) |

/data/config.xml

| 取决于底层的

ApplicationContext

|

资源路径不带前缀
ApplicationContext ctx = new ClassPathXmlApplicationContext("conf/appContext.xml");

上面这个案例资源路径不带前缀,

ClassPathXmlApplicationContext

将使用

ClassPathResource

加载在类路径下的资源文件。再来一个案例。

ApplicationContext ctx = new FileSystemXmlApplicationContext("conf/appContext.xml");

FileSystemXmlApplicationContext

将使用

URLResouce

来加载文件系统中的配置文件,这里案例是相对路径。

资源路径带前缀

当指定了资源前缀,则使用指定前缀对应的

Resource

来加载资源。如下案例。

ApplicationContext ctx =new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");

对比上一个案例,此处将使用

ClassPathResource

加载在类路径下的资源文件。

资源路径带前缀和通配符

指定单一的资源文件通过前面2种方式,若需要指定多个资源则可以考虑使用通配符,支持

Ant-style

和

classpath*

。

  1. Ant-style

    通配符

    /WEB-INF/*-context.xmlcom/mycompany/**/applicationContext.xmlfile:C:/some/path/*-context.xmlclasspath:com/mycompany/**/applicationContext.xml

    当使用

    Ant-style

    通配符,解析器遵循更复杂的过程来尝试解析通配符。它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个

    URL

    。它为直到最后一个非通配符段的路径生成一个资源,并从中获取一个

    URL

    。

    最后一个非通配符段的路径分2种情况处理:

      普通的文件路径: 生成

      java.io.File

      ,如

      classpath:com/mycompany/**/applicationContext.xml

      取到

      classpath:com/mycompany

      ,然后进行遍历。

    • 特殊的文件

      jar:

      路径:如

      classpath:com/mycompany/**/applicationContext.xml

      ,取到

      classpath:com/mycompany

      ,将生成

      java.net.JarURLConnection

      ,或是手动解析 jar

      URL

      然后遍历 jar 文件的内容来解析通配符。

      使用 jar

      URL

      强烈建议测试下是否能正常通过通配符访问到资源

  2. classpath*

    前缀

    如下案例

    ApplicationContext ctx =new ClassPathXmlApplicationContext("classpath*:conf/appContext.xml");

    遍历类路径下所有匹配到

    conf/appContext.xml

    的资源都会加载。

  3. 两种方式可以组合使用

    如

    classpath*:META-INF/*-beans.xml

    解决策略相当简单:在最后一个非通配符路径段上使用 ClassLoader.getResources() 调用来获取类加载器层次结构中的所有匹配资源,然后对于每个资源,

    PathMatcher

    解析策略用于通配符子路径。感兴趣的可以了解下

    PathMatcher

    类。

    组合使用容易掉坑的2个地方,请注意:

      classpath*:*.xml

      可能不会从 类路径下的jar 文件的根目录中检索文件,而只能从扩展目录的根目录中检索文件。

    • classpath:com/mycompany/**/service-context.xml

      如果要搜索的根包在多个类路径位置中可用,则不保证资源能够找到匹配的资源。 使用

      ClassLoader#getResource(com/mycompany/)

      如果多个路径有,则可能只返回第一个,其它的漏掉了。保险的做法是

      classpath*:com/mycompany/**/service-context.xml

彩蛋

出于向后兼容的历史原因,

FileSystemXmlApplicationContext

关联的

FileSystemResource

会将所有的资源的非前缀路径统一当做相对路径。上案例

// 2种写法是一样的ApplicationContext ctx =new FileSystemXmlApplicationContext("conf/context.xml");ApplicationContext ctx =new FileSystemXmlApplicationContext("/conf/context.xml");// 2种写法是一样的ctx.getResource("some/resource/path/myTemplate.txt");ctx.getResource("/some/resource/path/myTemplate.txt");// 可以强制不按相对路径处理不,可以的!按URLResource处理ctx = new FileSystemXmlApplicationContext("file:///conf/context.xml");

总结

本文主要详细说明了

  1. Resource

    接口的定义和方法说明

  2. Resource

    接口的内置6种实现

  3. ResourceLoader

    接口和实现

  4. ResourceLoaderAware

    接口使用

  5. 如何使用
    Resource

    配置

    ApplicationContext

这一篇下来,基本Spring关于资源处理和相关接口总体是比较清晰了。

知识分享,转载请注明出处。学无先后,达者为先!

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » Spring系列18:Resource接口及内置实现