Spring源码分析(二)资源加载利器Resource接口

2023年 9月 2日 99.1k 0

微信图片_20230403162032.jpg

  • 本图:川西旅游中拍摄的(业余摄影)
  • 官网:Home
  • 参考书籍:Spring源码深度解析-郝佳编著-微信读书

上一篇文章我们对Spring的基本架构有了基本的了解,以及完成了源码分析的基本环境的搭建,接下来我们开始源码分析,以案例来驱动来学习源码的知识
参考文章:spring5 源码深度解析----- IOC 之 容器的基本实现

  • 参考官网:Resources :: Spring Framework

1:本文章主要介绍一下Spring中对资源的定义,回想我们看Mybatis的源码分析是,一切的开始都是对资源的解析,加载,封装开始的,可以说这就是程序运行的地基,万丈高楼从地起,我们要熟悉这个思想
2:其实在官方文档中对资源的定义进行了详细的解释:springdoc.cn/spring/core…

一 准备工作

1.1 基本案例搭建

🔷先建测试包,我们就在源码项目中进行自己的测试用例的编写
image.png
🔷新建Spring-config.xm配置文件







image.png
🔷编写一个Bean,并配置Bean,测试是否可以管理我们的Bean对象

package org.springframework.shu;

/**
* @description: 测试Bean
* @author: shu
* @createDate: 2023/4/3 14:54
* @version: 1.0
*/
public class MyTestBean {
    private String name = "EasonShu";

    public MyTestBean(){
        System.out.println("创建对象");
    }

    public void setName(String name) {
        System.out.println("调用方法");
        this.name = name;
    }

    public void sayHello(){
        System.out.println("Hello!" + name);
    }


    public String getName() {
        return this.name;
    }

}

🔷配置我们的Bean




  
  
    
    
  


🔷测试

package org.springframework.shu;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
* @description: 测试Bean
* @author: shu
* @createDate: 2023/4/3 14:56
* @version: 1.0
*/
public class AppTest {
    @Test
    public void MyTestBeanTest() {
        BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml"));
        MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean");
        System.out.println(myTestBean.getName());
    }
}

🔷测试结果
image.png

二 Resource接口

Java的标准 java.net.URL 类和各种URL前缀的标准处理程序,不幸的是,还不足以满足对低级资源的所有访问。
例如,没有标准化的 URL 实现可用于访问需要从classpath或相对于 ServletContext 获得的资源。
总结来说就是Java自带的资源库满足不了Spring的要求,而Spring自己封装了对Resource接口

2.1 资源的定义

资源粗略的可以分为(这里以Spring的分类为例)

  • URL资源
  • File资源
  • ClassPath相关资源
  • 服务器相关资源(JBoss AS 5.x上的VFS资源)
  • ......
  • JDK操纵底层资源基本就是java.net.URL 、java.io.File 、java.util.Properties这些:取资源基本是根据绝对路径或当前类的相对路径来取。从类路径或Web容器上下文中获取资源的时候也不方便。**若直接使用这些方法,需要编写比较多的额外代码,例如前期文件存在判断、相对路径变绝对路径,**而Spring提供的Resource接口提供了更强大的访问底层资源的能力,首先我们来看看Jdk方法
    image.png

    2.1.1 Class

    public java.net.URL getResource(String name) {
            name = resolveName(name);
            ClassLoader cl = getClassLoader0();
            if (cl==null) {
                // A system class.
                return ClassLoader.getSystemResource(name);
            }
            return cl.getResource(name);
        }
    
    
         public InputStream getResourceAsStream(String name) {
            name = resolveName(name);
            ClassLoader cl = getClassLoader0();
            if (cl==null) {
                // A system class.
                return ClassLoader.getSystemResourceAsStream(name);
            }
            return cl.getResourceAsStream(name);
        }
    
    
        private String resolveName(String name) {
            if (name == null) {
                return name;
            }
            if (!name.startsWith("/")) {
                Class c = this;
                while (c.isArray()) {
                    c = c.getComponentType();
                }
                String baseName = c.getName();
                int index = baseName.lastIndexOf('.');
                if (index != -1) {
                    name = baseName.substring(0, index).replace('.', '/')
                        +"/"+name;
                }
            } else {
                name = name.substring(1);
            }
            return name;
        }
    

    简单来说他就是依靠类加载器的能力来加载资源,并且是当前类的路径相关的,也是支持以/开头的绝对路径的,我们在框架的源码很容易看到他的身影
    🌈🌈案例

    /**
     * @description: Jdk资源加载测试
     * @author: shu
     * @createDate: 2023/4/3 18:56
     * @version: 1.0
     */
    public class JdkResourceTest {
    	public static void main(String[] args) {
    		// 依赖Jdk的Class进行资源加载
    		InputStream asStream = JdkResourceTest.class.getResourceAsStream("/spring-config.xml");
    		System.out.println(asStream);
    		URL url = JdkResourceTest.class.getResource("/spring-config.xml");
    		System.out.println(url);
    		URL resource = JdkResourceTest.class.getResource("/spring-config.xml");
    		System.out.println(resource);
    	}
    }
    

    image.png

    2.1.2 ClassLoader

        public static URL getSystemResource(String name) {
            ClassLoader system = getSystemClassLoader();
            if (system == null) {
                return getBootstrapResource(name);
            }
            return system.getResource(name);
        }
    
    
       public static InputStream getSystemResourceAsStream(String name) {
            URL url = getSystemResource(name);
            try {
                return url != null ? url.openStream() : null;
            } catch (IOException e) {
                return null;
            }
        }
    

    🌈🌈案例

    package org.springframework.shu;
    
    import java.io.InputStream;
    import java.net.URL;
    
    /**
     * @description: 类加载器测试
     * @author: shu
     * @createDate: 2023/4/3 19:03
     * @version: 1.0
     */
    public class ClassLoaderTest {
    	public static void main(String[] args) {
    		URL url = ClassLoader.getSystemResource("spring-config.xml");
    		System.out.println(url);
    		InputStream stream = ClassLoader.getSystemResourceAsStream("spring-config.xml");
    		System.out.println(stream);aa
    	}
    }
    
    

    image.png
    需要注意的是:把java项目打包成jar包,如果jar包中存在资源文件需要访问,必须采取stream的形式访问。可以调用getResourceAsStream()方法,而不能采用路径的方式访问(文件已经被打到jar里面了,不符合路径的)。

    2.1.3 File

    这种方式我们应该非常熟悉,这里我就不多介绍了

    package org.springframework.shu;
    
    import java.io.File;
    
    /**
     * @description: 文件测试
     * @author: shu
     * @createDate: 2023/4/3 19:08
     * @version: 1.0
     */
    public class FileTest {
    	public static void main(String[] args) {
    		File file = new File("D:\workspace\spring-framework\spring-framework-5.2.0.RELEASE\spring-core\src\main\java\org\springframework\core\io\AbstractFileResolvingResource.java");
    		System.out.println(file.exists());
    	}
    }
    

    📌📌注意:

  • 不管是类对象的getResource()还是类加载器的getSystemResouce(),都是走的类加载器的getResource(),类加载器会搜索自己的加载路径来匹配寻找项。而最常用的类加载器就是AppClassLoader,又因为APPClassLoader的加载路径是classpath,所以网上文章一般都会说getClass().getResouce()是返回classpath,这是不够准确的。
  • 整体来说,JDK提供的一些获取资源的方式,还是比较难用的。如果你处在Spring环境中,强烈建议使用它提供的资源访问接口,下面着重介绍,我们首先编写一个测试用例
  • 2.2 Resource接口

    org.springframework.core.io. 包中的Spring Resource 接口,旨在成为一个更有能力的接口,用于抽象访问低级资源。

    
    
    package org.springframework.core.io;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URI;
    import java.net.URL;
    import java.nio.channels.Channels;
    import java.nio.channels.ReadableByteChannel;
    
    import org.springframework.lang.Nullable;
    
    /**
     * 资源接口:
     * 在Spring Framework中,资源是一个接口,它抽象了对底层资源的访问,如文件系统、类路径、Web应用程序、
     */
    public interface Resource extends InputStreamSource {
    
    
        /**
    	 * 是否存在
    	 * @return
    	 */
        boolean exists();
    
    
        /**
    	 * 是否可读
    	 * @return
    	 */
        default boolean isReadable() {
            return exists();
        }
    
    
        /**
    	 * 是否打开
    	 * @return
    	 */
        default boolean isOpen() {
            return false;
        }
    
    
        /**
    	 * 是否是文件
    	 * @return
    	 */
        default boolean isFile() {
            return false;
        }
    
    
        /**
    	 * 获取URL
    	 * @return
    	 * @throws IOException
    	 */
        URL getURL() throws IOException;
    
    
        /**
    	 * 获取URI
    	 * @return
    	 * @throws IOException
    	 */
        URI getURI() throws IOException;
    
    
        /**
    	 * 获取文件
    	 * @return
    	 * @throws IOException
    	 */
        File getFile() throws IOException;
    
    
        /**
    	 * 获取可读字节通道
    	 * @return
    	 * @throws IOException
    	 */
        default ReadableByteChannel readableChannel() throws IOException {
            return Channels.newChannel(getInputStream());
        }
    
    
        /**
    	 * 获取资源长度
    	 * @return
    	 * @throws IOException
    	 */
        long contentLength() throws IOException;
    
    
        /**
    	 * 上次修改时间
    	 * @return
    	 * @throws IOException
    	 */
        long lastModified() throws IOException;
    
    
        /**
    	 * 创建相对路径的资源
    	 * @param relativePath
    	 * @return
    	 * @throws IOException
    	 */
        Resource createRelative(String relativePath) throws IOException;
    
    
        /**
    	 * 获取文件名
    	 * @return
    	 */
        @Nullable
        String getFilename();
    
    
        /**
    	 * 获取描述
    	 * @return
    	 */
        String getDescription();
    
    }
    
    

    正如 Resource 接口的定义所示,它扩展了 InputStreamSource 接口,也及时上层接口

    
    
    package org.springframework.core.io;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    /**
     * 用于获取文件输入流的接口
     */
    public interface InputStreamSource {
    
    	/**
    	 * 获取文件输入流
    	 * @return
    	 * @throws IOException
    	 */
    	InputStream getInputStream() throws IOException;
    
    }
    
    

    我们可以看见他对InputStream进行了封装,可以转换成流数据
    Resource 接口中最重要的一些方法是。

    • getInputStream(): 定位并打开资源,返回一个用于读取资源的 InputStream。我们期望每次调用都能返回一个新的 InputStream。关闭该流是调用者的责任。
    • exists(): 返回一个 boolean 值,表示该资源是否以物理形式实际存在。
    • isOpen(): 返回一个 boolean,表示该资源是否代表一个具有开放流的句柄。如果为 true,InputStream 不能被多次读取,必须只读一次,然后关闭以避免资源泄漏。对于所有通常的资源实现,除了 InputStreamResource 之外,返回 false。
    • getDescription(): 返回该资源的描述,用于处理该资源时的错误输出。这通常是全路径的文件名或资源的实际URL。

    下面主要介绍他的几大分支结构
    image.png
    当涉及Spring Framework中的资源管理时,除了Resource接口本身外,还有几个主要的子接口和实现类,用于特定类型的资源管理和访问。以下是这些接口和实现之间的区别:

  • AbstractResource:
    AbstractResource是一个抽象类,它提供了Resource接口的基本实现,同时也可以作为其他自定义资源实现的基类。它处理了大部分资源操作的共通逻辑,但并没有提供直接的资源访问逻辑。这个类通常用于自定义资源实现时继承。
  • ContextResource:
    ContextResource接口是Resource接口的子接口之一,用于表示基于Spring应用程序上下文的资源,通常指的是ApplicationContext中定义的资源。它扩展了Resource接口,添加了一些用于管理资源在应用程序上下文中的注册和访问的方法。
  • HttpResource:
    HttpResource接口是Resource接口的子接口之一,用于表示HTTP资源,例如通过URL访问的远程资源。它通常用于访问Web上的文件,图像,API等。这个接口可以处理与HTTP相关的特定操作,如获取HTTP头信息等。
  • WritableResource:
    WritableResource接口也是Resource接口的子接口之一,用于表示可写的资源,即可以通过它来写入数据。这个接口扩展了Resource接口,添加了一些用于向资源写入数据的方法。
  • 这些接口和抽象类之间的关系可以总结如下:

    • AbstractResource是一个抽象类,提供了Resource接口的基本实现和通用逻辑。
    • ContextResourceHttpResourceWritableResource都是Resource接口的子接口,它们在特定情境下扩展了Resource接口,为特定类型的资源提供了更丰富的功能。

    在Spring应用程序中,根据需要,您可以使用这些不同的资源接口和实现来处理不同类型的资源,从而更方便地进行资源的加载、访问和管理。
    📌基本案例

    package org.springframework.shu;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.beans.factory.xml.XmlBeanFactory;
    import org.springframework.core.io.ClassPathResource;
    
    /**
     * @description: 测试Bean
     * @author: shu
     * @createDate: 2023/4/3 14:56
     * @version: 1.0
     */
    public class AppTest {
    	@Test
    	public void MyTestBeanTest() {
    		BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml"));
    		MyTestBean myTestBean = (MyTestBean) bf.getBean("myTestBean");
    		System.out.println(myTestBean.getName());
    	}
    }
    
    

    我们从代码中可以看到首先将我们编写的配置文件进行加载,我们来看看他是如何实现的,首先我们先来看看下面的接口
    image.png
    🔷**InputStreamSource:**对InputStream的包装,接口获取InputStream信息

    public interface InputStreamSource {
    
    	// 返回一个流数据
    	InputStream getInputStream() throws IOException;
    
    }
    
    

    🔷**Resource:**定义了一些基本的文件操作方法

    public interface Resource extends InputStreamSource {
        //返回Resource所指向的底层资源是否存在
      boolean exists();
      //返回当前Resource代表的底层资源是否可读
      default boolean isReadable() {
        return true;
      }
      //返回Resource资源文件是否已经打开,**如果返回true,则只能被读取一次然后关闭以避免内存泄漏;**常见的Resource实现一般返回false
      default boolean isOpen() {
        return false;
      }
      //@since 5.0  参见:getFile()
      default boolean isFile() {
        return false;
      }
      //如果当前Resource代表的底层资源能由java.util.URL代表,则返回该URL,否则抛出IO异常
      URL getURL() throws IOException;
      //如果当前Resource代表的底层资源能由java.util.URI代表,则返回该URI,否则抛出IO异常
      URI getURI() throws IOException;
      //如果当前Resource代表的底层资源能由java.io.File代表,则返回该File,否则抛出IO异常
      File getFile() throws IOException;
    
      //@since 5.0  用到了nio得Channel相关的
      default ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(getInputStream());
      }
      // 返回当前Resource代表的底层文件资源的长度,一般是值代表的文件资源的长度
      long contentLength() throws IOException;
      //返回当前Resource代表的底层资源的最后修改时间
      long lastModified() throws IOException;
    
      // 用于创建相对于当前Resource代表的底层资源的资源
      // 比如当前Resource代表文件资源“d:/test/”则createRelative(“test.txt”)将返回表文件资源“d:/test/test.txt”Resource资源。
      Resource createRelative(String relativePath) throws IOException;
    
      //返回当前Resource代表的底层文件资源的文件路径,比如File资源“file://d:/test.txt”将返回“d:/test.txt”,而URL资源http://www.javass.cn将返回“”,因为只返回文件路径。
      @Nullable
      String getFilename();
      //返回当前Resource代表的底层资源的描述符,通常就是资源的全路径(实际文件名或实际URL地址)
      String getDescription();
    }
    

    🔷**AbstractResource: **直接抽象类实现类子类的方法

    public abstract class AbstractResource implements Resource {
    
      // File或者流  都从此处判断
      // 这里属于通用实现,子类大都会重写这个方法的~~~~~~
      @Override
      public boolean exists() {
        // Try file existence: can we find the file in the file system?
        try {
          return getFile().exists();
        } catch (IOException ex) {
          // Fall back to stream existence: can we open the stream?
          try {
            InputStream is = getInputStream();
            is.close();
            return true;
          } catch (Throwable isEx) {
            return false;
          }
        }
      }
    
      // 默认都是可读得。大多数子类都会复写
      @Override
      public boolean isReadable() {
        return true;
      }
    
      // 默认不是打开的。 比如InputStreamResource就会让他return true
      @Override
      public boolean isOpen() {
        return false;
      }
      // 默认不是一个File
      @Override
      public boolean isFile() {
        return false;
      }
    
      // 可议看到getURI方法一般都是依赖于getURL的
      @Override
      public URL getURL() throws IOException {
        throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
      }
    
      @Override
      public URI getURI() throws IOException {
        URL url = getURL();
        try {
          return ResourceUtils.toURI(url);
        } catch (URISyntaxException ex) {
          throw new NestedIOException("Invalid URI [" + url + "]", ex);
        }
      }
    
      @Override
      public File getFile() throws IOException {
        throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
      }
      @Override
      public ReadableByteChannel readableChannel() throws IOException {
        return Channels.newChannel(getInputStream());
      }
      // 调用此方法,也相当于吧流的read了一遍,请务必注意
      @Override
      public long contentLength() throws IOException {
        InputStream is = getInputStream();
        try {
          long size = 0;
          byte[] buf = new byte[255];
          int read;
          while ((read = is.read(buf)) != -1) {
            size += read;
          }
          return size;
        } finally {
          try {
            is.close();
          } catch (IOException ex) {
          }
        }
      }
      @Override
      public long lastModified() throws IOException {
        long lastModified = getFileForLastModifiedCheck().lastModified();
        if (lastModified == 0L) {
          throw new FileNotFoundException(getDescription() +
              " cannot be resolved in the file system for resolving its last-modified timestamp");
        }
        return lastModified;
      }
      // 只有一个子类:`AbstractFileResolvingResource`覆盖了此方法
      protected File getFileForLastModifiedCheck() throws IOException {
        return getFile();
      }
      @Override
      public Resource createRelative(String relativePath) throws IOException {
        throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
      }
      @Override
      @Nullable
      public String getFilename() {
        return null;
      }
      // 这些基础方法,很多子类也都有重写~~~~ 但是一般来说关系不大
      @Override
      public String toString() {
        return getDescription();
      }
      // 比较的就是getDescription()
      @Override
      public boolean equals(Object obj) {
        return (obj == this ||
          (obj instanceof Resource && ((Resource) obj).getDescription().equals(getDescription())));
      }
      // getDescription()的hashCode
      @Override
      public int hashCode() {
        return getDescription().hashCode();
      }
    
    }
    

    以0AbstractResource为主要分支,下面我们仔细来介绍一下他的子类,
    image.png

    2.3 主要分支

    Resource.drawio.png
    下面介绍主要的资源类,其余的需要读者自己去看源码,其实也比较简单,我们主要的是学习这种思想

    2.3.1 FileSystemResource

    这是 java.io 的 Resource 实现。文件句柄。它还支持 java.nio.file。路径句柄,应用 Spring 的标准基于 String 的路径转换,但是通过 java.nio.file 执行所有操作,文件 API。
    对于纯 java.nio.path,基于路径的支持使用 PathResource,FileSystemResource 支持将解析作为文件和 URL。

    代表java.io.File资源,对于getInputStream操作将返回底层文件的字节流,isOpen将永远返回false,从而表示可多次读取底层文件的字节流。

    public class FileSystemResource extends AbstractResource implements WritableResource {
    
        private final String path;
    
    	@Nullable
    	private final File file;
    
    	private final Path filePath;
    
    	// 构造器
        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();
    	}
    
    
    	// 是否存在
      @Override
    	public boolean exists() {
    		return (this.file != null ? this.file.exists() : Files.exists(this.filePath));
    	}
    
    	// 是否可读
    	@Override
    	public boolean isReadable() {
    		return (this.file != null ? this.file.canRead() && !this.file.isDirectory() :
    				Files.isReadable(this.filePath) && !Files.isDirectory(this.filePath));
    	}
    
    
    	@Override
    	public InputStream getInputStream() throws IOException {
    		try {
    			return Files.newInputStream(this.filePath);
    		}
    		catch (NoSuchFileException ex) {
    			throw new FileNotFoundException(ex.getMessage());
    		}
    	}
    
    	@Override
    	public boolean isWritable() {
    		return (this.file != null ? this.file.canWrite() && !this.file.isDirectory() :
    				Files.isWritable(this.filePath) && !Files.isDirectory(this.filePath));
    	}
    
    
    	@Override
    	public OutputStream getOutputStream() throws IOException {
    		return Files.newOutputStream(this.filePath);
    	}
    
    }
    

    他的主要作用就是构建File,可以仔细查看源码
    Demo:

    package org.springframework.shu.resource;
    
    import org.springframework.core.io.FileSystemResource;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2023/4/3 19:53
     * @version: 1.0
     */
    public class FileSystemResourceTest {
    	public static void main(String[] args) {
    		FileSystemResource fileSystemResource = new FileSystemResource("E:\Spring源码学习\integration-tests\src\test\java\org\springframework\shu\resource\FileSystemResourceTest.java");
    		System.out.println(fileSystemResource.getFile());
    		System.out.println(fileSystemResource.getFilename());
    		System.out.println(fileSystemResource.getDescription());
    		System.out.println(fileSystemResource.exists());
    	}
    }
    
    

    image.png

    2.3.2 InputStreamResource

    InputStreamResource 是给定 InputStream 的 Resource 实现。只有在没有特定的 Resource 实现可用的情况下才应该使用它。
    特别是,在可能的情况下,更喜欢 ByteArrayResource 或任何基于文件的 Resource 实现。

    InputStreamResource代表java.io.InputStream字节流,对于“getInputStream ”操作将直接返回该字节流,因此只能读取一次该字节流,即“isOpen”永远返回true。

    public class InputStreamResource extends AbstractResource {
    
    	private final InputStream inputStream;
    
    	private final String description;
    
    	private boolean read = false;
    
    
        @Override
      	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;
      }
    }
    

    这个也比较简单就是把他转换成InputStream
    Demo

    package org.springframework.shu.resource;
    
    import org.springframework.core.io.InputStreamResource;
    
    import java.io.*;
    import java.nio.file.Files;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2023/4/3 20:01
     * @version: 1.0
     */
    public class InputStreamResourceTest {
    	public static void main(String[] args) throws IOException {
    		File file = new File("E:\Spring源码学习\integration-tests\src\test\java\org\springframework\shu\resource\InputStreamResourceTest.java");
    		 InputStream inputStream = Files.newInputStream(file.toPath());
    		InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
    		System.out.println(inputStreamResource.getInputStream());
    	}
    }
    

    image.png

    2.3.3 BeanDefinitionResource

    这就是把配置文件转成我们熟悉的Bean,BeanDefinition描述了一个bean实例,它具有属性值、构造函数参数值以及具体实现提供的进一步信息,关于这一块我们后面会详细介绍,这里先有个印象

    class BeanDefinitionResource extends AbstractResource {
    
    	private final BeanDefinition beanDefinition;
    
        /**
    	 * Create a new BeanDefinitionResource.
    	 * @param beanDefinition the BeanDefinition object to wrap
    	 */
    	public BeanDefinitionResource(BeanDefinition beanDefinition) {
    		Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    		this.beanDefinition = beanDefinition;
    	}
    
    	/**
    	 * Return the wrapped BeanDefinition object.
    	 */
    	public final BeanDefinition getBeanDefinition() {
    		return this.beanDefinition;
    	}
    
    
    	@Override
    	public boolean exists() {
    		return false;
    	}
    
    	@Override
    	public boolean isReadable() {
    		return false;
    	}
    
    	@Override
    	public InputStream getInputStream() throws IOException {
    		throw new FileNotFoundException(
    				"Resource cannot be opened because it points to " + getDescription());
    	}
    
    	@Override
    	public String getDescription() {
    		return "BeanDefinition defined in " + this.beanDefinition.getResourceDescription();
    	}
    
    
    	/**
    	 * This implementation compares the underlying BeanDefinition.
    	 */
    	@Override
    	public boolean equals(@Nullable Object other) {
    		return (this == other || (other instanceof BeanDefinitionResource &&
    				((BeanDefinitionResource) other).beanDefinition.equals(this.beanDefinition)));
    	}
    
    	/**
    	 * This implementation returns the hash code of the underlying BeanDefinition.
    	 */
    	@Override
    	public int hashCode() {
    		return this.beanDefinition.hashCode();
    	}
    
    }
    

    我们可以看到其实就是将我们配置的Bean属性转换成Bean实例,后面详细介绍

    2.2.4 DescriptiveResource

    简单资源实现,保存资源描述,但不指向实际可读的资源。

    public class DescriptiveResource extends AbstractResource {
    
    	private final String description;
    
    
    	/**
    	 * Create a new DescriptiveResource.
    	 * @param description the resource description
    	 */
    	public DescriptiveResource(@Nullable String description) {
    		this.description = (description != null ? description : "");
    	}
    
    
    	@Override
    	public boolean exists() {
    		return false;
    	}
    
    	@Override
    	public boolean isReadable() {
    		return false;
    	}
    
    	@Override
    	public InputStream getInputStream() throws IOException {
    		throw new FileNotFoundException(
    				getDescription() + " cannot be opened because it does not point to a readable resource");
    	}
    
    	@Override
    	public String getDescription() {
    		return this.description;
    	}
    
    
    	/**
    	 * This implementation compares the underlying description String.
    	 */
    	@Override
    	public boolean equals(@Nullable Object other) {
    		return (this == other || (other instanceof DescriptiveResource &&
    				((DescriptiveResource) other).description.equals(this.description)));
    	}
    
    	/**
    	 * This implementation returns the hash code of the underlying description String.
    	 */
    	@Override
    	public int hashCode() {
    		return this.description.hashCode();
    	}
    
    }
    
    

    这个就不介绍了,其实很简单

    2.2.5 ByteArrayResource

    这是给定字节数组的 Resource 实现。它为给定字节数组创建 ByteArrayInputStream。

    ByteArrayResource代表byte[]数组资源,对于“getInputStream”操作将返回一个ByteArrayInputStream。

    public class ByteArrayResource extends AbstractResource {
    
    	private final byte[] byteArray;
    
    	private final String description;
    
    
    	/**
    	 * Create a new {@code ByteArrayResource}.
    	 * @param byteArray the byte array to wrap
    	 */
    	public ByteArrayResource(byte[] byteArray) {
    		this(byteArray, "resource loaded from byte array");
    	}
    
    	/**
    	 * Create a new {@code ByteArrayResource} with a description.
    	 * @param byteArray the byte array to wrap
    	 * @param description where the byte array comes from
    	 */
    	public ByteArrayResource(byte[] byteArray, @Nullable String description) {
    		Assert.notNull(byteArray, "Byte array must not be null");
    		this.byteArray = byteArray;
    		this.description = (description != null ? description : "");
    	}
    
    
    	/**
    	 * Return the underlying byte array.
    	 */
    	public final byte[] getByteArray() {
    		return this.byteArray;
    	}
    
    	/**
    	 * This implementation always returns {@code true}.
    	 */
    	@Override
    	public boolean exists() {
    		return true;
    	}
    
    	/**
    	 * This implementation returns the length of the underlying byte array.
    	 */
    	@Override
    	public long contentLength() {
    		return this.byteArray.length;
    	}
    
    	/**
    	 * This implementation returns a ByteArrayInputStream for the
    	 * underlying byte array.
    	 * @see java.io.ByteArrayInputStream
    	 */
    	@Override
    	public InputStream getInputStream() throws IOException {
    		return new ByteArrayInputStream(this.byteArray);
    	}
    
    	/**
    	 * This implementation returns a description that includes the passed-in
    	 * {@code description}, if any.
    	 */
    	@Override
    	public String getDescription() {
    		return "Byte array resource [" + this.description + "]";
    	}
    
    
    	/**
    	 * This implementation compares the underlying byte array.
    	 * @see java.util.Arrays#equals(byte[], byte[])
    	 */
    	@Override
    	public boolean equals(@Nullable Object other) {
    		return (this == other || (other instanceof ByteArrayResource &&
    				Arrays.equals(((ByteArrayResource) other).byteArray, this.byteArray)));
    	}
    
    	/**
    	 * This implementation returns the hash code based on the
    	 * underlying byte array.
    	 */
    	@Override
    	public int hashCode() {
    		return (byte[].class.hashCode() * 29 * this.byteArray.length);
    	}
    
    }
    
    

    它可多次读取数组资源,即isOpen()永远返回false
    ByteArrayResource因为入参可以是byte[]类型,所以用途比较广泛,可以把从网络或者本地资源都转换为byte[]类型,然后用ByteArrayResource转化为资源

    2.2.6 PathResource

    这是 java.nio.file 的 Resource 实现,路径句柄,通过路径 API 执行所有操作和转换。它支持作为 File 和 URL 的解析,并且还实现了扩展的 WritableResource 接口。
    PathResource 实际上是一个纯 java.nio.path。具有不同创建相对行为的基于路径的 FileSystemResource 替代方案。

    它是基于@since 4.0,也是基于JDK7提供的java.nio.file.Path的。实现原理也非常的简单,更像是对java.nio.file.Path进行了包装(java.nio.file.Files)

    public class PathResource extends AbstractResource implements WritableResource {
    
    	private final Path path;
    
        public PathResource(Path path) {
    		Assert.notNull(path, "Path must not be null");
    		this.path = path.normalize();
    	}
    
    }
    

    2.2.7 AbstractFileResolvingResource

    它复写了AbstractResource大多数方法,是一个比较重要的分支。有不少非常好用的实现类
    image.png

    public abstract class AbstractFileResolvingResource extends AbstractResource {

    @Override
    public boolean exists() {
    try {
    URL url = getURL();
    if (ResourceUtils.isFileURL(url)) {
    // Proceed with file system resolution
    return getFile().exists();
    }
    else {
    // Try a URL connection content-length header
    URLConnection con = url.openConnection();
    customizeConnection(con);
    HttpURLConnection httpCon =
    (con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
    if (httpCon != null) {
    int code = httpCon.getResponseCode();
    if (code == HttpURLConnection.HTTP_OK) {
    return true;
    }
    else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
    return false;
    }
    }
    if (con.getContentLengthLong() > 0) {
    return true;
    }
    if (httpCon != null) {
    // No HTTP OK status, and no content-length header: give up
    httpCon.disconnect();
    return false;
    }
    else {
    // Fall back to stream existence: can we open the stream?
    getInputStream().close();
    return true;
    }
    }
    }
    catch (IOException ex) {
    return false;
    }
    }

    @Override
    public boolean isReadable() {
    try {
    return checkReadable(getURL());
    }
    catch (IOException ex) {
    return false;
    }
    }

    boolean checkReadable(URL url) {
    try {
    if (ResourceUtils.isFileURL(url)) {
    // Proceed with file system resolution
    File file = getFile();
    return (file.canRead() && !file.isDirectory());
    }
    else {
    // Try InputStream resolution for jar resources
    URLConnection con = url.openConnection();
    customizeConnection(con);
    if (con instanceof HttpURLConnection) {
    HttpURLConnection httpCon = (HttpURLConnection) con;
    int code = httpCon.getResponseCode();
    if (code != HttpURLConnection.HTTP_OK) {
    httpCon.disconnect();
    return false;
    }
    }
    long contentLength = con.getContentLengthLong();
    if (contentLength > 0) {
    return true;
    }
    else if (contentLength == 0) {
    // Empty file or directory -> not considered readable...
    return false;
    }
    else {
    // Fall back to stream existence: can we open the stream?
    getInputStream().close();
    return true;
    }
    }
    }
    catch (IOException ex) {
    return false;
    }
    }

    @Override
    public boolean isFile() {
    try {
    URL url = getURL();
    if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    return VfsResourceDelegate.getResource(url).isFile();
    }
    return ResourceUtils.URL_PROTOCOL_FILE.equals(url.getProtocol());
    }
    catch (IOException ex) {
    return false;
    }
    }

    /**
    * This implementation returns a File reference for the underlying class path
    * resource, provided that it refers to a file in the file system.
    * @see org.springframework.util.ResourceUtils#getFile(java.net.URL, String)
    */
    @Override
    public File getFile() throws IOException {
    URL url = getURL();
    if (url.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    return VfsResourceDelegate.getResource(url).getFile();
    }
    return ResourceUtils.getFile(url, getDescription());
    }

    /**
    * This implementation determines the underlying File
    * (or jar file, in case of a resource in a jar/zip).
    */
    @Override
    protected File getFileForLastModifiedCheck() throws IOException {
    URL url = getURL();
    if (ResourceUtils.isJarURL(url)) {
    URL actualUrl = ResourceUtils.extractArchiveURL(url);
    if (actualUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    return VfsResourceDelegate.getResource(actualUrl).getFile();
    }
    return ResourceUtils.getFile(actualUrl, "Jar URL");
    }
    else {
    return getFile();
    }
    }

    /**
    * This implementation returns a File reference for the given URI-identified
    * resource, provided that it refers to a file in the file system.
    * @since 5.0
    * @see #getFile(URI)
    */
    protected boolean isFile(URI uri) {
    try {
    if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    return VfsResourceDelegate.getResource(uri).isFile();
    }
    return ResourceUtils.URL_PROTOCOL_FILE.equals(uri.getScheme());
    }
    catch (IOException ex) {
    return false;
    }
    }

    /**
    * This implementation returns a File reference for the given URI-identified
    * resource, provided that it refers to a file in the file system.
    * @see org.springframework.util.ResourceUtils#getFile(java.net.URI, String)
    */
    protected File getFile(URI uri) throws IOException {
    if (uri.getScheme().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
    return VfsResourceDelegate.getResource(uri).getFile();
    }
    return ResourceUtils.getFile(uri, getDescription());
    }

    /**
    * This implementation returns a FileChannel for the given URI-identified
    * resource, provided that it refers to a file in the file system.
    * @since 5.0
    * @see #getFile()
    */
    @Override
    public ReadableByteChannel readableChannel() throws IOException {
    try {
    // Try file system channel
    return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
    }
    catch (FileNotFoundException | NoSuchFileException ex) {
    // Fall back to InputStream adaptation in superclass
    return super.readableChannel();
    }
    }

    @Override
    public long contentLength() throws IOException {
    URL url = getURL();
    if (ResourceUtils.isFileURL(url)) {
    // Proceed with file system resolution
    File file = getFile();
    long length = file.length();
    if (length == 0L && !file.exists()) {
    throw new FileNotFoundException(getDescription() +
    " cannot be resolved in the file system for checking its content length");
    }
    return length;
    }
    else {
    // Try a URL connection content-length header
    URLConnection con = url.openConnection();
    customizeConnection(con);
    return con.getContentLengthLong();
    }
    }

    @Override
    public long lastModified() throws IOException {
    URL url = getURL();
    boolean fileCheck = false;
    if (ResourceUtils.isFileURL(url) || ResourceUtils.isJarURL(url)) {
    // Proceed with file system resolution
    fileCheck = true;
    try {
    File fileToCheck = getFileForLastModifiedCheck();
    long lastModified = fileToCheck.lastModified();
    if (lastModified > 0L || fileToCheck.exists()) {
    return lastModified;
    }
    }
    catch (FileNotFoundException ex) {
    // Defensively fall back to URL connection check instead
    }
    }
    // Try a URL connection last-modified header
    URLConnection con = url.openConnection();
    customizeConnection(con);
    long lastModified = con.getLastModified();
    if (fileCheck && lastModified == 0 && con.getContentLengthLong()

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论