Spring IoC资源管理之Resource

简介: Spring IoC资源管理之Resource

概述


在学 Java SE 的时候我们学习了一个标准类 java.net.URL,该类在 Java SE 中的定位为统一资源定位器(Uniform Resource Locator),但是我们知道它的实现基本只限于网络形式发布的资源的查找和定位。然而,实际上资源的定义比较广泛,除了网络形式的资源,还有二进制形式、文件形式、  字节流形式存在的等等。而且它们可以存在于任何场所,比如网络、文件系统、应用程序中。所以  java.net.URL 的局限性迫使 Spring 必须实现自己的资源管理策略,资源管理应该满足如下要求:

  1. 职能划分清楚。资源的定义和资源的加载应该要有一个清晰的界限;
  2. 统一的抽象。统一的资源定义和资源加载策略。资源加载后要返回统一的抽象给客户端,客户端要对资源进行怎么的处理,应该由抽象资源接口来界定。


Resource


org.springframework.core.io.Resource 为 Spring 框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource接口。作为所有资源的统一抽象,Resource 定义了一些通用的方法,由子类 AbstractResource  提供统一的默认实现。定义如下:

public interface Resource extends InputStreamSource {
    boolean exists();
    default boolean isReadable() {
        return this.exists();
    }
    default boolean isOpen() {
        return false;
    }
    default boolean isFile() {
        return false;
    }
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String var1) throws IOException;
    @Nullable
    String getFilename();
    String getDescription();
}
复制代码


Resource 结构图如下:


image.png


从上图可以看到,Resource 根据资源的不同类型提供不同的具体实现,如下:

  • FileSystemResource :对  java.io.File 类型的资源的封装,只要是和 File 有关联,基本上与 FileSystemResource 也有关联。支持解析为 File 和 URL,实现扩展 WritableResource 接口。注意:从 Spring Framework 5.0开始,FileSystemResource 实现使用 NIO.2 API 进行读/写交互。从5.1开始,它可以使用一个 Path 句柄构造,在这种情况下, 它将通过NIO.2执行所有文件系统交互,仅`File`依靠on getFile()  。


  • ByteArrayResource:对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。


  • UrlResource:对 java.net.URL 类型资源的封装。内部委派 URL 进行具体的资源操作。


  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。


  • InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。


  • PathResource:ResourcePath 句柄的实现,通过 PathAPI 执行所有操作和转换。支持解析为 File 和 URL。实现扩展 WritableResource 接口。注意:从5.1版本开始,Path 也支持 FileSystemResource,因此不再推荐使用 PathResource,赞成使用  `FileSystemResource.FileSystemResource(Path)`


  • ServletContextResource : 为访问 Web 容器上下文中的资源而设计的类,负责以相对于 Web 应用根目录的路径加载资源,它支持以流和 URL 的方式访问,在 war 解包的情况下,也可以通过 File 的方式访问,该类还可以直接从 jar 包中访问资源。针对于 ServletContext 封装的资源,用于访问 ServletContext 环境下的资源。 ServletContextResource 持有一个 ServletContext 的引用 ,其底层是通过 ServletContext 的 getResource()方法和 getResourceAsStream()方法来获取资源的。


假设现在有一个资源在 Web 应用的类路径下,我们可以有几种方式来访问这个资源呢?

  1. 通过 FileSystemResource 以文件系统绝对路径的方式进行访问(如: D:/spring/WebRoot/WEB-INF/classes/conf.xml)
  2. 通过 ClassPathResource 以类路径的方式进行访问(如:conf.xml)
  3. 通过 ServletContextResource 以相对于 Web 应用根目录的方式进行访问(如: ServletContextResource(ServletContext,"/WEB-INF/classes/conf.xml”)) ,可以通过 ContextLoader.getCurrentWebApplicationContext().getServletContext()来获取 ServletContext。


AbstractResource


AbstractResource 为 Resource 接口的默认实现,它实现了 Resource 接口的大部分的公共实现,作为 Resource 接口中的重中之重,其定义如下:

public abstract class AbstractResource implements Resource {
    public AbstractResource() {
    }
       //如果是文件类型,则判断是否存在,后则关闭对应的流
    public boolean exists() {
        Log logger;
        if (this.isFile()) {
            try {
                return this.getFile().exists();
            } catch (IOException var4) {
                logger = LogFactory.getLog(this.getClass());
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not retrieve File for existence check of " + this.getDescription(), var4);
                }
            }
        }
        try {
            this.getInputStream().close();
            return true;
        } catch (Throwable var3) {
            logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Could not retrieve InputStream for existence check of " + this.getDescription(), var3);
            }
            return false;
        }
    }
    //直接返回true,表示可读
    public boolean isReadable() {
        return this.exists();
    }
    //直接返回false,表示未打开
    public boolean isOpen() {
        return false;
    }
    //直接返回false,表示不是File
    public boolean isFile() {
        return false;
    }
    //抛出 FileNotFoundException 异常,交给子类实现
    public URL getURL() throws IOException {
        throw new FileNotFoundException(this.getDescription() + " cannot be resolved to URL");
    }
    //基于 getURL() 返回的 URL 构建 URI
    public URI getURI() throws IOException {
        URL url = this.getURL();
        try {
            return ResourceUtils.toURI(url);
        } catch (URISyntaxException var3) {
            throw new NestedIOException("Invalid URI [" + url + "]", var3);
        }
    }
    //抛出 FileNotFoundException 异常,交给子类实现
    public File getFile() throws IOException {
        throw new FileNotFoundException(this.getDescription() + " cannot be resolved to absolute file path");
    }
    //根据 getInputStream() 的返回结果构建 ReadableByteChannel
    public ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(this.getInputStream());
    }
    //获取资源的长度
    public long contentLength() throws IOException {
        InputStream is = this.getInputStream();
        boolean var16 = false;
        long var6;
        try {
            var16 = true;
            long size = 0L;
            byte[] buf = new byte[256];
            while(true) {
                int read;
                if ((read = is.read(buf)) == -1) {
                    var6 = size;
                    var16 = false;
                    break;
                }
                size += (long)read;
            }
        } finally {
            if (var16) {
                try {
                    is.close();
                } catch (IOException var17) {
                    Log logger = LogFactory.getLog(this.getClass());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not close content-length InputStream for " + this.getDescription(), var17);
                    }
                }
            }
        }
        try {
            is.close();
        } catch (IOException var18) {
            Log logger = LogFactory.getLog(this.getClass());
            if (logger.isDebugEnabled()) {
                logger.debug("Could not close content-length InputStream for " + this.getDescription(), var18);
            }
        }
        return var6;
    }
    //返回资源最后的修改时间
    public long lastModified() throws IOException {
        File fileToCheck = this.getFileForLastModifiedCheck();
        long lastModified = fileToCheck.lastModified();
        if (lastModified == 0L && !fileToCheck.exists()) {
            throw new FileNotFoundException(this.getDescription() + " cannot be resolved in the file system for checking its last-modified timestamp");
        } else {
            return lastModified;
        }
    }
    protected File getFileForLastModifiedCheck() throws IOException {
        return this.getFile();
    }
    public Resource createRelative(String relativePath) throws IOException {
        throw new FileNotFoundException("Cannot create a relative resource for " + this.getDescription());
    }
    @Nullable
    public String getFilename() {
        return null;
    }
    public boolean equals(@Nullable Object other) {
        return this == other || other instanceof Resource && ((Resource)other).getDescription().equals(this.getDescription());
    }
    public int hashCode() {
        return this.getDescription().hashCode();
    }
    public String toString() {
        return this.getDescription();
    }
}
复制代码


如果我们想要实现自定义的 Resource,记住不要实现 Resource 接口,而应该继承 AbstractResource 抽象类,然后根据当前的具体资源特性覆盖相应的方法即可。


ClassPathResource


一个应用上下文构造器一般需要一个构成 Bean 定义的 xml 文件字符串路径或者一个字符串数组路径作为参数。


当这样的路径没有前缀的时候,那么从哪个路径构建的资源类型,用于加载 bean 的定义,取决于它所处的指定的上下文环境,例如,如果你像下面一样创建一个ClassPathXMLApplicationContext :

ApplicationContext context = new ClassPathXmlApplicationContext("application_context.xml");
//或者下面这个方式
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application_context.xml");
复制代码


Bean 定义将会从 classpath 中加载然后形成一个 ClassPathResource 来使用。基于此种情况,我们对 ClassPathResource 学习了解一下。


ClassPathResource 类是对 classpath 下资源的封装,或者是说对 ClassLoader.getResource()方法或 Class.getResource()方法的封装 ,它支持在当前 classpath 中读取资源文件。可以传入相对 classpath 的文件全路径名和 ClassLoader 构建 ClassPathResource,或忽略 ClassLoader 采用默认ClassLoader(即DefaultResourceLoader),此时在 getInputStream()方法 实现时会使用 ClassLoader.getSystemResourceAsStream(path)方法。


由于使用 ClassLoader 获取资源时默认相对于 classpath 的根目录,因而构造函数会忽略开头的“/”字符。ClassPathResource 还可以使用文件路径和 Class 作为参数构建,此时文件路径需要以“/”开头,表示该文件为相对于classpath 的绝对路径,否则为相对 Class 实例的相对路径,然后程序会报错,在 getInputStream()方法实现时使用 Class.getResourceAsStream()方法。


先看一个案例:

@Test
public void getResource() throws IOException {
    //ClassPathResource
    ClassPathResource resource = new ClassPathResource("application_context.xml");
    //        ClassPathResource resource = new ClassPathResource("/application_context.xml", User.class);
    InputStream input = resource.getInputStream();
    Assert.assertNotNull(input);
    System.out.println(resource.getClassLoader());
    System.out.println(resource.getPath());
}
复制代码


上述两种构建方式都是正确的,分别对应 ClassPathResource 的两个构造方法,源码如下:


public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
    Assert.notNull(path, "Path must not be null");
    String pathToUse = StringUtils.cleanPath(path);
    if (pathToUse.startsWith("/")) {
        pathToUse = pathToUse.substring(1);
    }
    this.path = pathToUse;
    this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader();
}
public ClassPathResource(String path, @Nullable Class<?> clazz) {
    Assert.notNull(path, "Path must not be null");
    this.path = StringUtils.cleanPath(path);
    this.clazz = clazz;
}
复制代码


接着再来查看 getInputStream()方法源码,


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(this.getDescription() + " cannot be opened because it does not exist");
    } else {
        return is;
    }
}
复制代码


关于获取资源的方式有两种:Class 获取和 ClassLoader 获取。


package ioc_study;
public class ResourceTest {
    @Test
    public void getResource() {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        System.out.println(classLoader.getResource("").getPath());
        System.out.println(this.getClass().getResource("").getPath());
        System.out.println(this.getClass().getResource("/").getPath());
        System.out.println(System.getProperty("user.dir"));
    }
}
复制代码


执行结果为:


/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/test-classes/
/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/test-classes/ioc_study/
/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/test-classes/
F:\workspace\Spmvc_Learn\spring_study\spring-chap1
复制代码


  • ClassLoader.getResource("")获取的是 classpath 的根路径
  • Class.getResource("")获取的是相对于当前类的相对路径
  • Class.getResource("/")获取的是 classpath 的根路径
  • System.getProperty("user.dir")获取的是项目的路径


相关文件的路径图如下:

image.png


关于 Class 和 ClassLoader 访问资源的区别,可以参考这篇文章:关于Class.getResource和ClassLoader.getResource的路径问题

在创建 ClassPathResource 对象时,我们可以指定是按 Class 的相对路径获取文件还是按 ClassLoader 来获取。


FileSystemResource


构建应用上下文除了使用 ClassPathXMLApplicationContext  之外,还可以使用 FileSystemXxmlApplicationContext ,代码如下:


ApplicationContext context = new ClassPathXmlApplicationContext("target/classes/application_context.xml");
//或者下面这个方式
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:target/classes/application_context.xml");
复制代码


FileSystemResourcey 会绑定到 FileSystemXxmlApplicationContext ,所以我们接下来学习一下 FileSystemResourcey 。


FileSystemResourcey 是对 File 的封装,在构建 FileSystemResourcey 时可以传入 File 对象或路径字符串(这里的路径可以是相对路径,相对路径是相对于  System.getProperty(“user.dir”) 的值所在的路径,也可以是绝对路径,也可以是“file:”开头的路径值),在内部会创建相应的 File 对象,并且计算其 path 值,这里的 path 是计算完“.”和“..”影响的值(规格化)。


看下面这个案例:


@Test
public void getResource() throws IOException {
   //        FileSystemResource resource1 = new FileSystemResource("target/classes/application_context.xml");
        FileSystemResource resource1 = new FileSystemResource("F:\\workspace\\Spmvc_Learn\\spring_study\\spring-chap1\\target\\classes\\application_context.xml");
    InputStream input = resource1.getInputStream();
    Assert.assertNotNull(input);
    System.out.println(resource1.getPath());
}
复制代码


接着查看其构造方法代码,


public FileSystemResource(String path) {
    Assert.notNull(path, "Path must not be null");
    this.path = StringUtils.cleanPath(path);
    this.file = new File(path);
    this.filePath = this.file.toPath();
}
public FileSystemResource(File file) {
    Assert.notNull(file, "File must not be null");
    this.path = StringUtils.cleanPath(file.getPath());
    this.file = file;
    this.filePath = file.toPath();
}
public FileSystemResource(Path filePath) {
    Assert.notNull(filePath, "Path must not be null");
    this.path = StringUtils.cleanPath(filePath.toString());
    this.file = null;
    this.filePath = filePath;
}
复制代码


在 getInputStream 方法中,使用该 File 对象创建 FileInputStream, 代码实现比较简单。


public InputStream getInputStream() throws IOException {
    try {
        return Files.newInputStream(this.filePath);
    } catch (NoSuchFileException var2) {
        throw new FileNotFoundException(var2.getMessage());
    }
}
复制代码


另外再介绍一下 createRelative 方法,源码如下:


public Resource createRelative(String relativePath) {
    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
    return this.file != null ? new FileSystemResource(pathToUse) : new FileSystemResource(this.filePath.getFileSystem(), pathToUse);
}
复制代码


createRelative 方法中使用 path 计算相对路径,其算法是:找到最后一个路径分隔符(/),将相对路径添加到该分隔符之后,传入的相对路径可以是以路径分割符(/)开头,也可以不以分隔符(/)开头,它们的效果是一样的,对相对路径存在的“.”和“..”会在创建FileSystemResource类时处理。最后,当使用将一个目录的 File 对象构建FileSystemResource 时,调用 createRelative 方法,其相对路径的父目录和当前 FileSystemResource 的父目录相同,比如使用”target/classes/application_context.xml”路径创建 FileSystemResource 对象,该 Resource 对象调用 createRelative,并传入”application_context.xml”,那么出现的结果为绝对路径。


@Test
public void getResource() throws IOException {
    System.out.println(System.getProperty("user.dir"));
    //FileSystemResource
    FileSystemResource resource1 = new FileSystemResource("target/classes/application_context.xml");//相对路径
    //FileSystemResource resource1 = new FileSystemResource("F:\\workspace\\Spmvc_Learn\\spring_study\\spring-chap1\\target\\classes\\application_context.xml");//绝对路径
    InputStream input = resource1.getInputStream();
    Assert.assertNotNull(input);
    System.out.println(resource1.getPath());
    System.out.println(resource1.getDescription());
    Resource resource = resource1.createRelative("application_context.xml");
    System.out.println(resource);
}
复制代码


执行结果为:


F:\workspace\Spmvc_Learn\spring_study\spring-chap1
target/classes/application_context.xml
file [F:\workspace\Spmvc_Learn\spring_study\spring-chap1\target\classes\application_context.xml]
file [F:\workspace\Spmvc_Learn\spring_study\spring-chap1\target\classes\application_context.xml]
复制代码


扩展:关于 FileSystemResource 的构建,如果不清楚文件的相对路径,还可以用以下方式进行实现。


@Test
public void getResource() throws IOException {
    URL url = getClass().getResource("application_context.xml");
    System.out.println("url.toURI():"+url.toURI());
    System.out.println("url.getPath():"+url.getPath());
    Path xmlPath = new File(url.getPath()).toPath();
    System.out.println("xmlPath:"+xmlPath);
    //这里传入的参数都是绝对路径
    FileSystemResource resource1 = new FileSystemResource(url.getPath());
    //   FileSystemResource resource1 = new FileSystemResource(xmlPath);
    System.out.println(resource1.getPath());
    System.out.println(resource1.getDescription());
}
复制代码


执行结果为:


url.toURI():file:/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/classes/application_context.xml
url.getPath():/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/classes/application_context.xml
xmlPath:F:\workspace\Spmvc_Learn\spring_study\spring-chap1\target\classes\application_context.xml
/F:/workspace/Spmvc_Learn/spring_study/spring-chap1/target/classes/application_context.xml
file [F:\workspace\Spmvc_Learn\spring_study\spring-chap1\target\classes\application_context.xml]
复制代码


目录
相关文章
|
18天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
38 2
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
39 0
|
2月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
144 9
|
2月前
|
存储 开发框架 Java
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
文章详细介绍了Spring、IOC、DI的概念和关系,解释了控制反转(IOC)和依赖注入(DI)的原理,并提供了IOC的代码示例,阐述了Spring框架作为IOC容器的应用。
38 0
什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
79 0
|
2月前
|
XML Java 数据格式
Spring的IOC和AOP
Spring的IOC和AOP
53 0
|
Java Spring
通过Spring Resource接口获取资源(6)
通过Spring Resource接口获取资源
1118 0
|
Java Spring 数据格式
Spring Resource接口获取资源
1.1.1. Resource简介  在Spring内部实现机制,针对于资源文件(配置的xml文件)有一个统一的接口Resource。   1.1.1.1. 接口定义的方法 1.exists():判断资源文件是否存在。
1602 0
|
Java Spring 数据格式
通过Spring Resource接口获取资源
通过Spring Resource接口获取资源 目录 1       Resource简介 2       通过ResourceLoader获取资源 3       在bean中获取Resource的方式   1       Resource简介        在Spring内部,针对于资源文件有一个统一的接口Resource表示。
922 0
下一篇
DataWorks