Spring Resource

Spring如何处理资源,资源的种类。

Java标准的java.net.URL并不足以处理底层资源,比如说没有从classpath或者相对于它的位置载入资源给ServletContext。
尽管可以给特定的前缀(比如http:)注册处理器(handler),但是这样会很复杂,而且URL接口缺少值得期待的功能,比如检查被指向的资源是否存在。

Spring Resource同样也在Spring Boot中广泛使用。

Resource接口

在org.springframework.core.io包里,是更适合用于抽象化存取底层资源的接口。

定义:

public interface Resource extends InputStreamSource {
    boolean exists();
    boolean isReadable();
    boolean isOpen();
    boolean isFile();
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    ReadableByteChannel readableChannel() throws IOException;
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    String getFilename();
    String getDescription();
}

InputStreamSource接口的定义:

public interface InputStreamSource {
    InputStream getInputStream() throws IOException;
}

Resource接口的部分重要的方法:

  • getInputStream():定位并打开资源,返回一个InputStream,调用者应记得close()这个流。
  • exists():返回一个boolean,表示资源是否真实存在。
  • isOpen():返回一个boolean,表示资源是否表示一个打开的流如果返回true,则InputStream不可以被多次读取,必须被读取一次,读取完之后要close防止资源泄漏。除了InputStreamResource的实现之外返回false(因为没有?2)。
  • getDescription():返回对于这个资源的描述,用于显示错误信息,通常是资源的全限定文件名或者资源的实际URL。

有的Resource实现同时实现了WritableResource接口,支持写入数据。

Spring处处使用Resource抽象作为方法参数,或者是接受一个String参数并转换成适当的Resource实现(简单方式),也或者通过特殊的前缀创建特定类型的Resource实现。
尽管Resource接口在Spring中普遍使用,但是实际上可以将Resource作为一个通用的工具用于自己的代码,而不用在意Spring的其他部分。
尽管会和Spring耦合,但也只是耦合这个小小的工具集,是URL接口更好的替代品,就和使用别的库一样。

内置Resource实现

UrlResource:包装了一个java.net.URL,可用于存取任何用url访问的资源,如文件、HTTPS、FTP等。
所有的URL都有一个标准化的String表示,有合适的前缀来表示URL类型,如:file:、https:、ftp:等。
除了通过构造器创建之外,接受用String表示路径的方法也会间接创建一个UrlResource对象。
通常一个路径String里面含有特定的前缀,就会自动创建特定的Resource实现类型。
如果使用了无法识别的路径前缀(prefix,如classpath:),就会自动创建UrlResource

ClassPathResource:表示从classpath获取的资源,使用当前线程的classloader,或给定的classloader,也或者给定的一个类用于加载资源。这个Resource实现支持将文件系统下的但是不在jar文件或者被(比如servlet)展开在文件系统下资源解析成java.io.File类型。可以通过构造器创建,但是在使用String作为路径的方法的参数里,使用classpath:前缀会隐式地创建。

FileSystemResource:用于处理java.io.File的实现,同样支持java.nio.file.Path。实施Spring标准化的字符串路径转换,但是所有的操作通过java.nio.file.Files实现。要获得纯正的java.nio.path.Path支持请用PathResource。FileSystemResource支持解析成FileURL(同时支持)。

PathResource:处理java.nio.file.Path的实现,通过Path API完成所有操作和转换。支持解析成FileURL(同时支持)。同时实现了WritableResource接口,支持写入数据。实际上就是基于java.nio.path.Path的FileSystemResource的替代,但是有不同的createRelative行为。

ServletContextResource:用于ServletContext资源的实现,将路径解释成从web应用根目录开始的相对路径。总是支持以stream存取和URL存取,仅当web应用被展开并且资源物理存在于文件系统里才能以java.io.File形式存取。但是,展开资源与否,还是直接读取jar文件,还是从数据库读取,其实取决于servlet容器。

InputStreamResource:用于处理InputStream的实现。仅在没有合适的Resource实现可用时才用。优先使用ByteArrayResource或者别的基于文件的Resource实现。为了和别的实现做出区分,这个实现是一个用于已经打开的资源的descriptor,所以调用isOpen()会返回true。除非要读取一个流许多次或者将descriptor用于别的地方,否则不要使用。

ByteArrayResource:用于处理给定字节数组的实现,为给定的字节数组创建ByteArrayInputStream。
多用于从别的地方读取一个数组而不用创建一个一次性的InputStreamResource。

ResourceLoader接口

被设计成可以返回(载入)Resource实例的接口。定义:

public interface ResourceLoader {
    Resource getResource(String location);
    ClassLoader getClassLoader();
}

所有的容器类型(ApplicationContext)都实现了ResourceLoader接口,所以可用于获取Resource实例。

当通过容器调用getResource(),路径表示的资源没有特殊前缀时,容器会返回对应于容器类型的Resource实例。例如:

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

假设ctx是一个ClassPathXmlApplicationContext类型实例,那么这个调用将返回ClassPathResource类型的Resource实例。如果ctx是FileSystemXmlApplicationContext类型,就返回FileSystemResource。对于WebApplicationContext类型则是返回ServletContextResource类型的实例。

可以通过添加特定的前缀来实现强制创建想要的类型:

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

添加了classpath:前缀就会创建ClassPathResource。

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

添加了file:前缀就用FileSystemResource

Resource template = ctx.getResource("https://myhost.com/resource/path/myTemplate.txt");

使用https:前缀就创建UrlResource。

String前缀转换成Resource对象的规则总结:

Prefix例子解释
classpath:classpath:com/myapp/config.xml从classpath载入资源
file:file:///data/config.xml以URL的形式从文件系统载入资源
https:https://myserver/logo.png作为URL载入
(none)/data/config.xml取决于具体的ApplicationContext(容器)实现

ResourcePatternResolver接口

是ResourceLoader的拓展,定义了将路径解析成Resource对象的策略。定义:

public interface ResourcePatternResolver extends ResourceLoader {
    String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
    Resource[] getResources(String locationPattern) throws IOException;
}

这个接口定义了一个特殊的叫classpath*:的前缀,用于匹配classpath下所有的资源。资源路径不能有占位符。jar文件或者classpath下的不同目录可能会有相同名称相同路径的文件。

PathMatchingResourcePatternResolver是一个单独的实现,在ApplicationContext之外也能使用,也被ResourceArrayPropertyEditor使用,用于获得Resource[]类型的bean的属性。
PathMatchingResourcePatternResolver可以将一个特定的路径解析成一个或多个匹配的Resource对象。
源路径可以是一对一映射到目标Resource,也可能是类似classpath*:之类的前缀或ant风格正则表达式。

注意:任何标准ApplicationContext之内的ResourceLoader实际上都是实现了ResourcePatternResolver接口的PathMatchingResourcePatternResolver实例。容器自己实际上也实现了ResourcePatternResolver接口,然后委托给默认的PathMatchingResourcePatternResolver。

ResourceLoaderAware接口

特殊的回调接口,用于识别能提供ResourceLoader引用的组件。定义:

public interface ResourceLoaderAware {
    void setResourceLoader(ResourceLoader resourceLoader);
}

当一个类实现了ResourceLoaderAware接口并作为bean注入到容器,它会被容器识别成ResourceLoaderAware。然后容器调用setResourceLoader(ResourceLoader)方法,将容器自己作为参数传进去,因为容器(ApplicationContext)也实现了ResourceLoader接口。
由于容器自己也是一个ResourceLoader,所以bean实现ApplicationContextAware接口也能达成同样目的。但是如果只是想载入资源,使用ResourceLoader接口会更好。这样代码只会和资源加载接口耦合,而不是整个ApplicationContext接口。

在应用组件里,可以通过@Autowired自动装配一个ResourceLoader类型的对象作为ResourceLoaderAware接口的替代。为了更有弹性(自动装配字段和多个方法参数),考虑使用基于注解的自动装配功能:

@Autowired
ResourceLoader resourceLoader;

注意:如果从一个路径下含有一个或多个Resource对象,考虑自动装配个ResourcePatternResolver实例而不是ResourceLoader。

资源作为依赖

如果bean要通过一系列动态处理来判断或者提供资源路径,那么bean使用ResourceLoader或者ResourcePatternResolver接口来装载资源就很合理。例如一个模版,对特定的用户载入特定的资源。

由于所有的容器类型都注册并使用PropertyEditor类型bean,可以将String路径转换成Resource对象。

例如,有一个MyBean类:

public class MyBean {
    private Resource template;
    public setTemplate(Resource template) {
        this.template = template;
    }
}

在xml配置中,template属性可以配置成:

<bean id="myBean" class="example.MyBean">
    <property name="template" value="some/resource/path/myTemplate.txt"/>
</bean>

由于没有特殊前缀,所以具体的类型取决于容器类型。如果需要强制转换,可以添加前缀:

<property name="template" value="classpath:some/resource/path/myTemplate.txt">

类型是ClassPathResource,或者:

<property name="template" value="file:///some/resource/path/myTemplate.txt"/>

类型是FileSystemResource。

甚至可以使用@Value注解注入String路径:

@Component
public class MyBean {
    private final Resource template;
    public MyBean(@Value("${template.path}") Resource template) {
        this.template = template;
    }
}

注入的String值由PropertyEditor转换,存在于Spring Environment。

如果想要装在路径下的多个资源,可以将注入点定义成数组类型:

@Component
public class MyBean {
    private final Resource[] templates;
    public MyBean(@Value("${templates.path}") Resource[] templates) {
        this.templates = templates;
    }
}

例如templates.path的值为classpath*:/config/templates/*.txt,可以载入目录下所有的txt文件资源。

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注