Spring源码学习--配置文件封装

文章引用:

1 http://spring.cndocs.ml/resources.html#resources-resource

Spring中配置文件的加载通过ClassPathResource进行封装的,例如:

Resource resource = new ClassPathResource("applicationContext.xml");

一、ClassPathResource的UML图如下所示

这里写图片描述

先来看看在Spring源码深度解析这本书中描述的为什么Spring要有ClassPathResource这个东西吧

在java中,将不同来源的资源抽象成URL,通过注册不同的handler(RULStreamHandler)来处理不同来源的读取逻辑,一般handler的类型使用不同前缀(协议,Protocol)来识别,如”file:”、”http:”、”jar:”等,然后URL没有默认定义相对ClassPath或者ServletContext等资源的handler,虽然可以注册自己的URLStreamHandler来解析特定的URL前缀(协议),比如”classpath:”,然后这需要了解URL的实现机制,而且URL也没有提供一些基本的方法,比如检查当前资源是否存在、检查当前资源是否可读等方法。因而Spring对其内部使用到的资源实现了自己的抽象结构:Resource接口来封装底层资源。

二、InputStreamSource接口

这是针对 InputStream 提供的 Resource 实现。建议,在确实没有找到其他合适的 Resource 实现时,才使用 InputSteamResource。如果可以,尽量选择 ByteArrayResource 或其他基于文件的 Resource 实现来代替。

与其他 Resource 实现已比较,InputStreamRsource 倒像一个已打开资源的描述符,因此,调用 isOpen() 方法会返回 true。除了在需要获取资源的描述符或需要从输入流多次读取时,都不要使用 InputStreamResource 来读取资源。

package org.springframework.core.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * InputStreamSource 接口定义了一种能力:InputStreamSource封装任何能够返回InputStream的类,比如
 * File\ClassPath下的资源和Byte和Array等。
 * 
 * 定位并且打开当前资源,返回当前资源的 InputStream。预计每一次调用都会返回一个新的 InputStream,
 * 因此关闭当前输出流就成为了调用者的责任。
 */

/**
 * Simple interface for objects that are sources for an {@link InputStream}.
 *
 * <p>This is the base interface for Spring's more extensive {@link Resource} interface.
 *
 * <p>For single-use streams, {@link InputStreamResource} can be used for any
 * given {@code InputStream}. Spring's {@link ByteArrayResource} or any
 * file-based {@code Resource} implementation can be used as a concrete
 * instance, allowing one to read the underlying content stream multiple times.
 * This makes this interface useful as an abstract content source for mail
 * attachments, for example.
 *
 * @author Juergen Hoeller
 * @since 20.01.2004
 * @see java.io.InputStream
 * @see Resource
 * @see InputStreamResource
 * @see ByteArrayResource
 */
public interface InputStreamSource {

    /**
     * Return an {@link InputStream}.
     * <p>It is expected that each call creates a <i>fresh</i> stream.
     * <p>This requirement is particularly important when you consider an API such
     * as JavaMail, which needs to be able to read the stream multiple times when
     * creating mail attachments. For such a use case, it is <i>required</i>
     * that each {@code getInputStream()} call returns a fresh stream.
     * @return the input stream for the underlying resource (must not be {@code null})
     * @throws IOException if the stream could not be opened
     * @see org.springframework.mail.javamail.MimeMessageHelper#addAttachment(String, InputStreamSource)
     */
    InputStream getInputStream() throws IOException;

}

三、Resource 接口

相对标准 url 访问机制,spring 的 Resource 接口对抽象底层资源的访问提供了一套更好的机制。

/**
 * Resource接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等。首先,它定义了3个判断当前资源状态的方法:
 * exists()、isReadable()、isOpen()。Resource接口还提供了不同资源到URL、URI、File类型的转换;
 * 以及获取lastModified属性、文件名(不带路径信息的文件名、getFilename()方法)。为了便于操作,Resource还提供了
 * 基于当前资源创建一个相对资源的方法:createRelative()。在错误处理中需要详细地打印出错的资源文件,因而Resource
 * 还提供了getDescription()方法用于在错误处理中的打印信息。
 */
public interface Resource extends InputStreamSource {

    /**
     * 判断当前资源是否存在
     */
    boolean exists();

    /**
     * 判断当前资源是否可读
     */
    boolean isReadable();

    /**
     * 判断当前资源是否打开状态
     */
    boolean isOpen();

    /**
     * 获取文件名称(不带路径信息的文件名)
     */
    String getFilename();

    /**
     * 返回当前资源的描述,当处理资源出错时,资源的描述会用于错误信息的输出。
     * 一般来说,资源的描述是一个完全限定的文件名称,或者是当前资源的真实 url。
     */
    String getDescription();

    long lastModified() throws IOException;

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    long contentLength() throws IOException;
}

四、内置的 Resource 实现

spring 直接提供了多种开箱即用的 Resource 实现。

1丶UrlResource

UrlResource 封装了一个 java.net.URL 对象,用来访问 URL 可以正常访问的任意对象,比如文件、an HTTP target, an FTP target, 等等。所有的 URL 都可以用一个标准化的字符串来表示。如通过正确的标准化前缀,可以用来表示当前 URL 的类型,当中就包括用于访问文件系统路径的 file:,通过 http 协议访问资源的 http:,通过 ftp 协议访问资源的 ftp:,还有很多……

可以显式化地使用 UrlResource 构造函数来创建一个 UrlResource,不过通常我们可以在调用一个 api 方法是,使用一个代表路径的 String 参数来隐式创建一个 UrlResource。对于后一种情况,会由一个 javabean PropertyEditor 来决定创建哪一种 Resource。如果路径里包含某一个通用的前缀(如 classpath:),PropertyEditor 会根据这个通用的前缀来创建恰当的 Resource;反之,如果 PropertyEditor 无法识别这个前缀,会把这个路径作为一个标准的 URL 来创建一个 UrlResource。

2丶ClassPathResource

可以使用 ClassPathResource 来获取类路径上的资源。ClassPathResource 可以使用线程上下文的加载器、调用者提供的加载器或指定的类中的任意一个来加载资源。

ClassPathResource 可以从类路径上加载资源,其可以使用线程上下文加载器、指定加载器或指定的 class 类型中的任意一个来加载资源。

当类路径上资源存于文件系统中,ClassPathResource 支持以 java.io.File 的形式访问,可当类路径上的资源存于尚未解压(没有 被Servlet 引擎或其他可解压的环境解压)的 jar 包中,ClassPathResource 就不再支持以 java.io.File 的形式访问。鉴于上面所说这个问题,spring 中各式 Resource 实现都支持以 jave.net.URL 的形式访问。

可以显式使用 ClassPathResource 构造函数来创建一个 ClassPathResource ,不过通常我们可以在调用一个 api 方法时,使用一个代表路径的 String 参数来隐式创建一个 ClassPathResource。对于后一种情况,会由一个 javabean PropertyEditor 来识别路径中 classpath: 前缀,从而创建一个 ClassPathResource。

3丶FileSystemResource

这是针对 java.io.File 提供的 Resource 实现。显然,我们可以使用 FileSystemResource 的 getFile() 函数获取 File 对象,使用 getURL() 获取 URL 对象。

4丶ServletContextResource

这是为了获取 web 根路径的 ServletContext 资源而提供的 Resource 实现。

ServletContextResource 完全支持以流和 URL 的方式访问,可只有当 web 项目是已解压的(不是以 war 等压缩包形式存在)且该 ServletContext 资源存于文件系统里,ServletContextResource 才支持以 java.io.File 的方式访问。至于说到,我们的 web 项目是否已解压和相关的 ServletContext 资源是否会存于文件系统里,这个取决于我们所使用的 Servlet 容器。若 Servlet 容器没有解压 web 项目,我们可以直接以 JAR 的形式的访问,或者其他可以想到的方式(如访问数据库)等。

5丶InputStreamResource

这是针对 InputStream 提供的 Resource 实现。建议,在确实没有找到其他合适的 Resource 实现时,才使用 InputSteamResource。如果可以,尽量选择 ByteArrayResource 或其他基于文件的 Resource 实现来代替。

与其他 Resource 实现已比较,InputStreamRsource 倒像一个已打开资源的描述符,因此,调用 isOpen() 方法会返回 true。除了在需要获取资源的描述符或需要从输入流多次读取时,都不要使用 InputStreamResource 来读取资源。

6丶ByteArrayResource

这是针对字节数组提供的 Resource 实现。可以通过一个字节数组来创建 ByteArrayResource。

当需要从字节数组加载内容时,ByteArrayResource 是一个不错的选择,使用 ByteArrayResource 可以不用求助于 InputStreamResource。

五、ClassPathResource

1、获取ClassLoader的几种方式:

this.getClass.getClassLoader();  // 使用当前类的ClassLoader   

Thread.currentThread().getContextClassLoader();  // 使用当前线程的ClassLoader   

ClassLoader.getSystemClassLoader();  // 使用系统ClassLoader,即系统的入口点所使用的ClassLoader。  

2、利用ClassLoader获取配置文件的InputeStream

InputStream is = null;
try {
    is = this.getClass().getClassLoader().getResourceAsStream("com/alexia/config/sys.properties");

    is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/alexia/config/sys.properties");

    is = ClassLoader.getSystemResourceAsStream("com/alexia/config/sys.properties");
} catch (Exception e) {

} finally {
    IOUtils.closeQuietly(is);
}

3、其中ClassPathResource中InputStreamSource接口中getInputStream()方法实现如下

    /**
     * This implementation opens an InputStream for the given class path resource.
     * @see java.lang.ClassLoader#getResourceAsStream(String)
     * @see java.lang.Class#getResourceAsStream(String)
     */
    @Override
    public InputStream getInputStream() throws IOException {
        InputStream is;
        if (this.clazz != null) {
            is = this.clazz.getResourceAsStream(this.path);
        }
        else if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        }
        else {
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }
        if (is == null) {
            throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
        }
        return is;
    }

4、上述this.clazz.getResourceAsStream(this.path)方法描述如下:

这里写图片描述

五、业务开发关联

学习源码的一个重要目的就是为了使用源码

在日常的开发工作中,资源文件的加载也是经常用到的,可以直接使用Spring提供的类,比如在希望加载文件时可以使用一下代码:

Resource resource = new ClassPathResource("applicationContext.xml");
InputStream inputStream = resource.getInputStream();

利用上述代码我们得到inputStream之后,我们就可以按照以前的开发方式进行实现,并且我们已经可以利用Resource以及子类为我们提供好的诸多特性。

有了Resource接口边可以对所有资源文件进行统一处理。至于实现,其实是非常简单的,以getInputStream为例,ClassPathResource中的实现方式便是通过class或者classloader提供的底层方法进行调用,而对于FileSystemResource的实现其实更简单,直接使用FileInputStream对文件进行实例化。

FileSystemResource中getInputStream方法实现如下:

    /**
     * This implementation opens a FileInputStream for the underlying file.
     * @see java.io.FileInputStream
     */
    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就完全交给XmlBeanDefinitionReader来处理了。

猜你喜欢

转载自blog.csdn.net/u013412772/article/details/80048603