一、什么是Resource

当我们学习Spring时,我们总会在xml中对bean进行声明,然后再通过下面的代码片段来获取bean。代码如下:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Context {

  public static void main(String[] args) {
    
    ApplicationContext ctx = new ClassPathXmlApplicationContext("Spring.xml");//1、加载配置文件
    System.err.println(ctx.getBean("stu").toString());//2、通过配置文件获取一个bean并打印

   }
}

上面第一行代码加载了一个资源文件Spring.xml,这个资源文件在Spring中属于Resource的一种。这样讲,Spring把各种类型的文件,二进制流都称之为Resource,只不过对于Spring开发者来说,Resource大多都是xml文件。

Resource是一个接口,接口里边定义了非常多关于文件的操作策略,比如

  • exists():判断资源是否存在
  • getURL():获取资源的URL
  • getFilename():获取资源的名称
  • ......(方法非常多,我就不一一列举了)

Resource 接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。

非常重要的一点,这也是Spring Resource设计最美妙的地方,那就是Spring 在Resource资源处理上采用啦策略模式,这也是我为什么上面把Resource接口中的方法称之为策略的原因了!

如果还不了解Java 策略模式的朋友请先自行学习!



二、Spring Resource接口类结构图

为了更好地理解,在这里我先放一张策略模式的UML图,它的具体内容本文不讲。

 

resource权限作用 resource reuse_resource权限作用

 

 

resource权限作用 resource reuse_resource权限作用_02

 

好了!图片放上去了,那么策略模式Resource体系上如何体现出来呢?

  • 1、Resource接口相当于我们的策略
  • 2、下面所有的实现类,包括AbstractResource在内都属于我们的具体策略实现
  • 3、你可能要问,那么我们的context角色到哪里去了呢?哈哈,做生意不能忘了我们的老本啊!它就是我们的IOC容器ApplicationContext,它是我们策略模式中最最最具有决策能力的老大了

至于为什么容器就可以处理我们的Resource,我接下去会说到!

很明显,Resource是资源的最高抽象,通常我们的应用程序不是都从classpath下去加载我们的xml文件,我们也可以通过一个URL,URI,InputStream等方式获取一个资源,这就是Spring把资源的获取方式和资源的定义之间解耦了!



三、ResourceLoader接口

  • ResourceLoader:该接口实现类的实例可以获得一个 Resource 实例。。

ResourceLoader 接口里有如下方法:

  • Resource getResource(String location):该接口仅包含这个方法,该方法用于返回一个 Resource 实例。

ApplicationContext 的实现类都实现 ResourceLoader 接口,而该接口又返回一个Resource实例,因此 ApplicationContext 可用于直接获取 Resource 实例,这也是为什么容器就可以处理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。



五、策略模式在Spring中使用有什么优势

Spring 应用需要进行资源访问时,实际上并不需要直接使用 Resource 实现类,而是调用 ApplicationContext 实例的 getResource() 方法来获得资源,ApplicationContext 将会负责选择 Resource 的实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来。

既然有那么多的优势,下面就在来解释我们上面的第一段代码,为什么要使用ClassPathXmlApplicationContext这个类来对资源进行加载?

我们都知道,在容器ApplicationContext下有很多的实现,比如FileSystemXmlApplicationContext,ClassPathXmlApplicationContext,XmlWebApplicationContext,而且我们加载配置文件的方式通常也是使用它们来进行加载,在Spring中,ApplicationContext的实现类顾名思义也是对应了我们Resource接口的一些实现策略,比如ClassPathResource,FileSystemResource等。