Spring源码学习5:BeanDefinitionRegister模块-Resource

BeanDefinition是怎么来的?

ReourceLoader装载配置文件
BeanDefinitionReader解析配置信息
配置文件
Resource
BeanDefinitionRegister
BeanDefinition

发现流程图中涉及到了4个主要的接口,齐心协力之下,把配置信息变成了我们熟悉的Bean定义,来学习一下吧:

  • ReourceLoader:用于加载资源(如XML配置文件)
  • Resource: 表示加载的资源
  • BeanDefinitionReader:读取资源中的bean定义
  • BeanDefinitionRegistry:用于注册和管理bean定义

什么是资源(Resource)?

在计算机科学和软件工程中,资源通常指的是程序或系统需要访问和使用的外部数据或文件。这些资源可以以多种形式存在,包括但不限于文件系统中的文件、数据库中的数据、网络上的远程文件或服务、类路径中的文件以及内存中的数据。这些资源可以包含配置文件、图像、文档、脚本、音频文件、视频文件等。

1. 类路径资源 (Classpath Resource)
描述:这些资源位于应用程序的类路径中,通常用于加载配置文件、静态资源等。例如,applicationContext.xml位于类路径中,可以通过classpath:applicationContext.xml进行访问。
2.文件系统资源 (File System Resource)
描述:这些资源位于文件系统中,可以通过绝对路径或相对路径进行访问。例如,/path/to/file.txt位于文件系统中,可以通过file:/path/to/file.txt进行访问。
3. URL资源 (URL Resource)
描述:这些资源通过URL进行访问,通常用于加载网络上的远程资源。例如,http://example.com/resource.txt位于网络上,可以通过http://example.com/resource.txt进行访问。
4. 字节数组资源 (Byte Array Resource)
描述:这些资源使用字节数组表示,可以直接在内存中操作。例如,使用new ByteArrayResource(byteArray)创建一个字节数组资源。
5. 输入流资源 (Input Stream Resource)
描述:这些资源使用输入流表示,可以直接从输入流中读取数据。例如,使用new InputStreamResource(inputStream)创建一个输入流资源。
6. ServletContext资源 (ServletContext Resource)
描述:这些资源位于Web应用的ServletContext中,通常用于加载Web应用中的配置文件或静态资源。例如,/WEB-INF/web.xml位于ServletContext中。

Resource实现类

资源类型实现类示例路径
1. 类路径资源ClassPathResourceclasspath:applicationContext.xml
2. 文件系统资源FileSystemResourcefile:/path/to/file.txt
3. Url资源UrlResourcehttp://example.com/resource.txt
4. 字节数组资源ByteArrayResourcenew ByteArrayResource(byteArray)
5. 输入流资源InputStreamResourcenew InputStreamResource(inputStream)
6. ServletContext资源ServletContextResource/WEB-INF/web.xml

统一接口:Resource

Resource接口为这些不同类型的资源提供了统一的操作方法。
"统一接口"说的就是:无论资源类型如何,开发者都可以使用相同的方法来检查资源的存在性、读取资源内容等。

public interface Resource extends InputStreamSource {

	资源是否存在
    boolean exists();

	资源是否可读
    boolean isReadable();

	资源是否已打开
    boolean isOpen();

	1. FileSystemResource:返回实例:file:/path/to/file.txt(文件系统资源)
	2. UrlResource:返回实例:http://example.com/resource.txt(URL资源)
	3. ByteArrayResource:返回实例:抛出FileNotFoundException异常
    URL getURL() throws IOException; 获取资源的URL
    URI getURI() throws IOException; 获取资源的URI

	获取资源对应的文件对象
    File getFile() throws IOException;

	获取资源的内容长度
    long contentLength() throws IOException;

	获取资源的最后修改时间
    long lastModified() throws IOException;

	根据相对路径创建相对于此资源的资源
    Resource createRelative(String relativePath) throws IOException;

	获取资源的文件名
    String getFilename();

	描述资源的内容或位置
	虽然方法名中包含了“描述”,但它并不会实际读取资源的内容或从资源中提取字符串。
	描述内容是指资源的类型和位置信息,而不是资源内部的数据内容。
    String getDescription();
}

方法说明:isOpen

资源是否已打开:打开资源意味着打开一个输入流来读取其内容

观察:

getInputStream()返回结果isOpen()返回结果
FileSystemResource每次返回一个新对象false
ClassPathResource每次返回一个新对象false
ByteArrayResource每次返回一个新对象false
InputStreamResource每次返回封装的对象true

问题:

  1. FileSystemResource的getInputStream()方法、InputStreamResource的getInputStream()方法有什么不同吗?
  2. 为什么调用getInputStream()方法之后,有些isOpen返回false,有些返回true?

解答:

先看看FileSystemResource 的源码:

public class FileSystemResource extends AbstractResource {

    private final File file;

    public FileSystemResource(File file) {
        this.file = file;
    }

	每次都是返回一个新的FileInputStream,且下面的isOpen永远返回false
    @Override
    public InputStream getInputStream() throws IOException {
        return new FileInputStream(this.file);
    }

	isOpen永远返回true@Override
    public boolean isOpen() {
        return false;
    }

    // 其他方法...
}

FileSystemResource:代表一个文件系统中的资源。每次调用getInputStream方法时,它都会创建一个新的FileInputStream实例。因此,资源的打开状态不依赖于FileSystemResource实例,而是依赖于每次调用时创建的新的输入流。所以isOpen方法必须返回false,因为资源的打开状态与FileSystemResource的实例无关。


再看看InputStreamResource 的源码:

public class InputStreamResource extends AbstractResource {

    private final InputStream inputStream;
    private boolean read = false;

    public InputStreamResource(InputStream inputStream) {
        this.inputStream = inputStream;
    }

	这里是依赖本身的,且会在getInputStream时候将read 设置为true
    @Override
    public InputStream getInputStream() {
        this.read = true;
        return this.inputStream;
    }

	默认为flase,调用getInputStream变为true
    @Override
    public boolean isOpen() {
        return this.read;
    }

    // 其他方法...
}

InputStreamResource:直接包装一个现有的InputStream。每次调用getInputStream方法时,它都是返回包装的同一个InputStream,依赖于自己,并且在第一次调用getInputStream之后,read改变为true,因此isOpen会返回true。

方法说明:getURI | getURL

  • getURI() 会返回一个用于标识资源的 URI;
  • getURL() 通常会返回一个可以用来定位资源的 URL,表示一个可访问的地址;
    • 只要getURL方法能返回一个URL,那就是可以访问的URL,否则,方法会抛出异常。类似于getFile、getInputStream都是返回一个可访问的对象;

观察:

对象getURL返回结果getURI返回结果说明
FileSystemResourcefile:/path/to/file.txtfile:/path/to/file.txt文件系统资源URL
UrlResourcehttp://example.com/resource.txthttp://example.com/resource.txtURL资源
ClassPathResourcefile:/path/to/your/project/target/classes/application.propertiesfile:/path/to/your/project/target/classes/application.properties类路径资源
ServletContextResourcefile:/path/to/your/servlet/context/resource抛出异常ServletContext资源
ByteArrayResource抛出异常抛出异常字节数组资源
InputStreamResource抛出异常抛出异常输入流资源

URL是什么

  • 统一资源定位符
  • 侧重点:指向一个 可以访问 的资源,所以包含了访问资源的必要信息(如http、https、ftp等),以及资源的位置(例如域名和路径)
  • URL示例:http://example.com/resource

URI 是什么?

  • 统一资源标识符
  • 侧重点:用于标识一个资源。URI 可以是一个 URL 或者 URN

使用案例

项目结构:

src/
└── main/
    └── resources/
        └── config.properties

config.properties 文件内容:

app.name=MyApp
app.version=1.0.0
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Properties;

public class ClassPathResourceExample {

    public static void main(String[] args) {
        // 创建 ClassPathResource 对象,指向类路径中的 config.properties 文件
        Resource resource = new ClassPathResource("config.properties");

        // 调用所有 Resource 接口的方法
        try {
            System.out.println("资源是否存在: " + resource.exists());

            System.out.println("资源是否可读: " + resource.isReadable());

            System.out.println("资源是否打开: " + resource.isOpen());

            URL url = resource.getURL();
            System.out.println("资源 URL: " + url);

            URI uri = resource.getURI();
            System.out.println("资源 URI: " + uri);

            File file = resource.getFile();
            System.out.println("资源文件信息: " + file.getAbsolutePath());

            long contentLength = resource.contentLength();
            System.out.println("资源内容长度: " + contentLength);

            long lastModified = resource.lastModified();
            System.out.println("资源最后修改时间: " + lastModified);

			//createRelative(): 创建一个相对于当前资源路径的新资源对象
            Resource relativeResource = resource.createRelative("anotherConfig.properties");
            System.out.println("创建一个相对于当前资源路径的新资源对象: " + relativeResource.getDescription());

            String filename = resource.getFilename();
            System.out.println("资源文件名: " + filename);

            // getDescription()
            String description = resource.getDescription();
            System.out.println("资源描述信息: " + description);

            // 读取文件内容
            try (InputStream inputStream = resource.getInputStream()) {
                Properties properties = new Properties();
                properties.load(inputStream);

                // 输出读取到的配置内容
                System.out.println("App Name: " + properties.getProperty("app.name"));
                System.out.println("App Version: " + properties.getProperty("app.version"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

getInputStream、getFile、getURL选择

  1. getInputStream()

    对文件的内容进行操作。提供一种通用的方式读取各种类型的资源,而不关心资源的实际存储位置。

  2. getFile()

    对文件进行操作,比如:文件复制、移动、删除等。 只有当资源实际存储在文件系统中时才能使用。如果资源不是文件系统中的文件,例如位于 JAR 包中的文件或网络资源,调用该方法会抛出异常。

  3. getURL()

    适用于需要将资源作为 URL 处理的场景,例如通过网络访问资源、传递资源的 URL 等。可以用于网络通信、资源定位等。

构造函数参数:绝对路径

  • 文件的完整路径,从文件系统的根目录开始。例如:
    • Windows: C:\Users\username\Documents\file.txt
    • Linux: /home/username/Documents/file.txt

绝对路径:以 / 开头的路径被视为绝对路径,直接指向文件系统中的位置。例如,/data/file.txt 会被解析为根目录下的 data/file.txt(在 Unix/Linux 系统中)。

构造函数参数:相对路径

相对路径:没有以 / 开头的路径被视为相对于当前工作目录的路径。例如,data/file.txt 会被解析为当前工作目录下的data/file.txt

什么叫 “当前工作目录” ?

工作目录:是指 Java 虚拟机(JVM)启动时的目录。这通常是运行 Java 命令的目录!例如:

包所在目录命令运行目录运行命令工作目录
/home/project/home/projectjava -jar myapp.jar/home/project
/home/project/homejava -jar /home/project/myapp.jar/home/project

“工作目录” = “包所在目录”

构造函数参数:没有“前缀”

在没有前缀的情况下,路径处理的方式取决于具体使用的 Resource 实现类。

  1. FileSystemResource:用于文件系统中的文件,当传递相对路径时,它会解释为相对于当前工作目录的路径。
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

public class FileSystemResourceExample {
    public static void main(String[] args) {
        Resource resource = new FileSystemResource("data/file.txt");
        printResourceDetails(resource);
    }

    private static void printResourceDetails(Resource resource) {
        try {
            System.out.println("File exists: " + resource.exists());
            System.out.println("URL: " + resource.getURL());
            System.out.println("File: " + resource.getFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. ClassPathResource:用于类路径中的资源,当传递相对路径时,它会解释为相对于类路径的路径。

    如果你传递 com/example/config.properties,ClassPathResource 会在类路径下的com/example目录中寻找这个文件。

src
└── main
    └── resources
        ├── config.properties
        └── com
            └── example
                └── config.properties

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class ClassPathResourceExample {
    public static void main(String[] args) {
        Resource resource = new ClassPathResource("com/example/config.properties");
        printResourceDetails(resource);
    }

    private static void printResourceDetails(Resource resource) {
        try {
            System.out.println("File exists: " + resource.exists());
            System.out.println("URL: " + resource.getURL());
            // Note: getFile() might not work for resources in JAR files
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  1. UrlResource
    当我们说“相对于当前 URL”的时候,这通常是指一个 URL 基于另一个 URL 的路径来解析。在 Web 开发中,相对路径是基于当前页面的 URL 来解析的,但在 Java 中使用 UrlResource 时,必须明确指定完整的 URL 或相对路径。
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.net.MalformedURLException;

public class UrlResourceExample {
    public static void main(String[] args) {
        try {
            // 创建一个绝对 URL 的资源
            Resource resource = new UrlResource("http://example.com/folder/resource.html");
            printResourceDetails(resource);

            // 如果你尝试使用相对路径,通常需要与一个基 URL 结合使用
            // 例如,构建一个新的 URL
            URL baseUrl = new URL("http://example.com/");
            URL relativeUrl = new URL(baseUrl, "otherpage.html");
            Resource relativeResource = new UrlResource(relativeUrl);
            printResourceDetails(relativeResource);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    private static void printResourceDetails(Resource resource) {
        try {
            System.out.println("Resource exists: " + resource.exists());
            System.out.println("Resource URL: " + resource.getURL());
            System.out.println("Resource Content Length: " + resource.contentLength());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

详细说明

  1. 绝对 URL:
    Resource resource = new UrlResource(“http://example.com/resource.txt”);
    这个构造函数直接使用绝对 URL,UrlResource 会尝试访问这个 URL 指向的资源。

  2. 相对 URL:
    在Web开发中,相对路径是基于当前页面的URL来解析的。例如,如果你在浏览 http://example.com/folder/resource.html,并且页面上有一个相对链接 …/otherpage.html,这个链接会被解析为 http://example.com/otherpage.html。

  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值