深入Spring IOC源码之Resource

简介:

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

public interface InputStreamSource {

    InputStream getInputStream() throws IOException;

}

public interface Resource extends InputStreamSource {

    boolean exists();

    boolean isReadable();

    boolean isOpen();

    URL getURL() throws IOException;

    URI getURI() throws IOException;

    File getFile() throws IOException;

    long lastModified() throws IOException;

    Resource createRelative(String relativePath) throws IOException;

    String getFilename();

    String getDescription();

}

InputStreamSource封装任何能返回InputStream的类,比如Fileclasspath下的资源、Byte Array等。它只有一个方法定义:getInputStream(),该方法返回一个新的InputStream对象。

Resource接口抽象了所有Spring内部使用到的底层资源:FileURLclasspath等。首先,它定义了三个判断当前资源状态的方法:存在性(exists)、可读性(isReadable)、是否处于打开状态(isOpen)。在C语言中,当我们拿到一个文件句柄时,我们要调用open方法打开文件才可以真正读取该文件,但是在Java中并没有显示的定义open方法,一般当我们创建一个InputStreamReader时,该资源(文件)就已经处于打开状态了,因而这里的isOpen方法并不是判断当前资源是否已经处于打开的可操作状态,这里是表示Resource接口所抽象的底层资源是否可以多次调用getInputStream()方法,如果该方法返回true,则不可以多次调用getInputStream()方法。在Spring 2.5.6的实现中,只有InputStreamResource类的isOpen()方法返回true,其余都返回false

另外,Resource接口还提供了不同资源到URLURIFile类型的转换,以及获取lastModified属性、文件名(不带路径信息的文件名,getFilename())的方法。为了便于操作,Resource还提供了基于当前资源创建一个相对资源的方法:createRelative();在错误处理中需要详细的打印出错的资源文件,因而Resource还提供了getDescription()方法用于在错误处理中的打印信息。

Spring 2.5.6中,所有实现Resource的接口类继承关系图如下:

即对不同来源的资源文件都有相应的Resource实现:文件(FileSystemResource)、classpath资源(ClassPathResource)、URL资源(UrlResource)、InputStream资源(InputStreamResource)、Byte数组(ByteArrayResource)等。

AbstractResource

AbstractResource是对Resource的基本实现,所有Resource实现类都继承了该类,所有继承该类的Resource一般只需要实现以下方法即可:

public File getFile() throws IOException

public URL getURL() throws IOException

public String getDescription()

public InputStream getInputStream() throws IOException

该类默认实现中,将toStringequalshashCode都代理给Description属性;isReadable总是返回true,而isOpen总是返回falseexists方法实现中,先调用getFile返回的File对象的exists方法,如果失败,查看是否可以获得InputStream,如果可以,返回true,否则,返回falsegetURLgetFilecreateRelative方法抛出FileNotFoundException,而getURI则代理给getURL方法。

ByteArrayResource

ByteArrayResource是一个简单的Resource实现,它是对二进制数组的封装,每次调用getInputStream时都会以这个二进制数组作为源创建一个ByteArrayInputStream。它的exists方法总是返回true,而且重写了equalshashCode的方法,以判断二进制数组的内容;它的description属性可以是用户自定义,也可以使用默认值:resource loaded from byte array

public final byte[] getByteArray() {

    return this.byteArray;

}

public boolean exists() {

    return true;

}

public InputStream getInputStream() throws IOException {

    return new ByteArrayInputStream(this.byteArray);

}

FileSystemResource

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

getInputStream方法中,使用该File对象创建FileInputStream;而path值作为description属性、equalshashCode等方法的实现;所有其他方法(existsisReadablegetURL等)都代理给File对象;createRelative方法中使用path计算相对路径,其算法是:找到最后一个路径分隔符(/),将相对路径添加到该分隔符之后,传入的相对路径可以是以路径分割符(/)开头,也可以不以分隔符(/)开头,他们的效果是一样的,对相对路径存在的“.”和“..”会在创建FileSystemResource类时处理。最后,当使用将一个目录的File对象构建FileSystemResource时,调用createRelative方法,其相对路径的父目录和当前FileSystemResource的父目录相同,比如使用”/home/Levin/dir1”目录创建FileSystemResource对象,该Resource对象调用createRelative,并传入”file”,那么出现的结果为”/home/Levin/file”,如果要得到”/home/Levin/dir1/file”,那么构建FileSystemResource时,应该传入”/home/Levin/dir1/”字符串。

public boolean isReadable() {

    return (this.file.canRead() && !this.file.isDirectory());

}

public InputStream getInputStream() throws IOException {

    return new FileInputStream(this.file);

}

public Resource createRelative(String relativePath) {

    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);

    return new FileSystemResource(pathToUse);

}

public String getDescription() {

    return "file [" + this.file.getAbsolutePath() + "]";

}

UrlResource

UrlResource是对URLURI的封装。在构建UrlResource时可以传入URLURIPath字符串(带协议字符串,如”file:”)。在UrlResource内部还会创建一个cleanedUrl,它是规格化(计算“.”和“..”后的值),该URL将会用于equalshashCode方法的实现。

getInputStream方法实现中,它使用URL.openConnection()方法获取URLConnection,后调用该URLConnectiongetInputStream方法。对getFile()方法,只支持文件系统的资源,即URL字符串的协议部分为”file:”UrlResource还支持从jarzipvfszipwsjar等内部文件,以jar为例,这些文件的字符串表达为:jar:file:/<jarpath>/jarfile.jar!/<filepath>/filename,如jar:file:/E:/Program%20Files/eclipse-juno/plugins/org.junit_4.10.0.v4_10_0_v20120426-0900/junit.jar!/org/junit/Test.class,然而这些内部文件本身并没有lastModified的属性,因而对这些内部文件,UrlResourcejarzip等文件的lastModified视为这些内部文件的lastModified属性。对createRelative方法,直接使用URL提供的构造函数,忽略传入的relativePath中的路径分隔符“/”。

public InputStream getInputStream() throws IOException {

    URLConnection con = this.url.openConnection();

    con.setUseCaches(false);

    return con.getInputStream();

}

protected File getFileForLastModifiedCheck() throws IOException {

    if (ResourceUtils.isJarURL(this.url)) {

        URL actualUrl = ResourceUtils.extractJarFileURL(this.url);

        return ResourceUtils.getFile(actualUrl);

    }

    else {

        return getFile();

    }

}

public Resource createRelative(String relativePath) throws MalformedURLException {

    if (relativePath.startsWith("/")) {

        relativePath = relativePath.substring(1);

    }

    return new UrlResource(new URL(this.url, relativePath));

}

ClassPathResource

classpath下资源的封装,或者说是对ClassLoader.getResource()方法或Class.getResource()方法的封装。它支持在当前classpath中读取资源文件。可以传入相对classpath的文件全路径名和ClassLoader构建ClassPathResource,或忽略ClassLoader采用默认ClassLoader(即Thread Context ClassLoader),此时在getInputStream()方法实现时时会使用ClassLoader.getResourceAsStream()方法,由于使用ClassLoader获取资源时默认相对于classpath的根目录,因而构造函数会忽略开头的“/”字符。ClassPathResource还可以使用文件路径和Class作为参数构建,此时若文件路径以“/”开头,表示该文件为相对于classpath的绝对路径,否则为相对Class实例的相对路径,在getInputStream()方法实现时使用Class.getResourceAsStream()方法。

getFile()方法只支持存在于文件系统中的资源;对lastModified的属性,若是jarzip等文件中的资源,则采用jarzip文件本身的lastModified属性;equals会同时判断pathclassloaderclazz字段,而hashCode则只使用path

public InputStream getInputStream() throws IOException {

    InputStream is = null;

    if (this.clazz != null) {

        is = this.clazz.getResourceAsStream(this.path);

    }

    else {

        is = this.classLoader.getResourceAsStream(this.path);

    }

    if (is == null) {

        throw new FileNotFoundException(

                getDescription() + " cannot be opened because it does not exist");

    }

    return is;

}

public URL getURL() throws IOException {

    URL url = null;

    if (this.clazz != null) {

        url = this.clazz.getResource(this.path);

    }

    else {

        url = this.classLoader.getResource(this.path);

    }

    if (url == null) {

        throw new FileNotFoundException(

                getDescription() + " cannot be resolved to URL because it does not exist");

    }

    return url;

}

public File getFile() throws IOException {

    return ResourceUtils.getFile(getURL(), getDescription());

}

protected File getFileForLastModifiedCheck() throws IOException {

    URL url = getURL();

    if (ResourceUtils.isJarURL(url)) {

        URL actualUrl = ResourceUtils.extractJarFileURL(url);

        return ResourceUtils.getFile(actualUrl);

    }

    else {

        return ResourceUtils.getFile(url, getDescription());

    }

}

public Resource createRelative(String relativePath) {

    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);

    return new ClassPathResource(pathToUse, this.classLoaderthis.clazz);

}

InputStreamResource

InputStreamResource是对InputStream的封装,它接收InputStream作为构造函数参数,它的isOpen总是返回true,并且只能被读取一次(即getInputStream方法只能被调用一次),existsisReadable方法也总是返回true。由于它不能被多次读取,只有当不用多次读取的时候才使用该类,并且只有当没有其他可用Resource类时才使用该类。在Spring内部貌似没有使用它。它只实现了getInputStream方法:

public InputStream getInputStream() throws IOException, IllegalStateException {

    if (this.read) {

        throw new IllegalStateException("InputStream has already been read - " +

                "do not use InputStreamResource if a stream needs to be read multiple times");

    }

    this.read = true;

    return this.inputStream;

}

DescriptiveResource

DescriptiveResource是对非物理资源的Description的封装。它实现了getDescription()方法。ResourceDescription属性主要用于错误处理时能更加准确的打印出错位置的信息,DescriptiveResource提供对那些需要提供Resource接口中的Description属性作为错误打印信息的方法自定义的描述信息。比如在BeanDefinitionReader中,在仅仅使用InputSource作为源加载BeanDefinition时,就可以使用DescriptiveResource定义自己的Description,从而在出错信息中可以方便的知道问题源在哪里。

BeanDefinitionResource

SpringResource可以用于非物理资源的抽,BeanDefinitionResource是对BeanDefinition的封装。BeanDefinitionResource类似DescriptiveResource,它也只实现了getDescription()方法,用于在解析某个BeanDefinition出错时显示错误源信息:

public String getDescription() {

    return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription();

}

ContextResource接口

Spring中还定义了ContextResource接口,继承自Resource接口,只包含一个方法:

public interface ContextResource extends Resource {

    String getPathWithinContext();

}

getPathWithContext()方法相对于Context的路径,如ServletContextPortletContextclasspathFileSystem等,在Spring core中它有两个实现类FileSystemContextResourceClassPathContextResource,他们分别是FileSystemResourceLoaderDefaultResourceLoader中的内部类,他们对getPathWithContext()方法的实现只是简单的返回path值。

另外,在Spring Web模块中,有一个ServletContextResource实现类,它使用ServletContextpath作为参数构造,getInputStreamgetURLgetURIgetFile等方法中将实现代理给ServletContext,其中getPathWithContext方法依然返回path字符串:

public boolean exists() {

    try {

        URL url = this.servletContext.getResource(this.path);

        return (url != null);

    }

    catch (MalformedURLException ex) {

        return false;

    }

}

public InputStream getInputStream() throws IOException {

    InputStream is = this.servletContext.getResourceAsStream(this.path);

    if (is == null) {

        throw new FileNotFoundException("Could not open " + getDescription());

    }

    return is;

}

public URL getURL() throws IOException {

    URL url = this.servletContext.getResource(this.path);

    if (url == null) {

        throw new FileNotFoundException(

                getDescription() + " cannot be resolved to URL because it does not exist");

    }

    return url;

}

public File getFile() throws IOException {

    String realPath = WebUtils.getRealPath(this.servletContextthis.path);

    return new File(realPath);

}

public Resource createRelative(String relativePath) {

    String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);

    return new ServletContextResource(this.servletContext, pathToUse);

}


相关文章
|
8月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
526 26
|
2月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
6月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
401 70
|
5月前
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
|
10月前
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
199 69
|
7月前
|
Java 容器 Spring
什么是Spring IOC 和DI ?
IOC : 控制翻转 , 它把传统上由程序代码直接操控的对象的调用权交给容 器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转 移,从程序代码本身转移到了外部容器。 DI : 依赖注入,在我们创建对象的过程中,把对象依赖的属性注入到我们的类中。
|
7月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
130 0
|
10月前
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
147 21
|
9月前
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
323 7