【小家Spring】资源访问利器---Spring提供的Resource接口以及它的常用子类源码分析(上)

简介: 【小家Spring】资源访问利器---Spring提供的Resource接口以及它的常用子类源码分析(上)

前言


资源是一个抽象的概念,什么是资源?我们已知Spring中有很多xml配置文件,同时还可能自建各种properties资源文件,还有可能进行网络交互,收发各种文件、二进制流等。


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


  1. URL资源
  2. File资源
  3. ClassPath相关资源
  4. 服务器相关资源(JBoss AS 5.x上的VFS资源)


JDK操纵底层资源基本就是java.net.URL 、java.io.File 、java.util.Properties这些:取资源基本是根据绝对路径或当前类的相对路径来取。从类路径或Web容器上下文中获取资源的时候也不方便。若直接使用这些方法,需要编写比较多的额外代码,例如前期文件存在判断、相对路径变绝对路径等。


而Spring提供的Resource接口提供了更强大的访问底层资源的能力。


JDK提供的资源访问简单案例



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;
    }


我们发现它采用的是当前的ClassLoader,并且是当前类的路径相关的,也是支持以/开头的绝对路径的。


说明,下面的例子基于:spring.properties在类路径下(也就是maven工程的resources目录下) demo.properties位于maven工程的java目录下,包名为:com/fsx/maintest/demo.properties


Demo


    public static void main(String[] args) {
        // 此处用相对路径,那就是相对Main所在的路径。因此此处需要demo.properties和Main.class文件在同一个包里面  否则请用对应的../../等
        // 这里Main所在包为:com.fsx.maintest  因此最终找的文件地址为:com/fsx/maintest/demo.properties 会去这里找文件
        URL resource = Main.class.getResource("demo.properties");
        System.out.println(resource); //file:/E:/work/remotegitcheckoutproject/myprojects/java/demo-war/target/classes/com/fsx/maintest/demo.properties
        // 若采用绝对路径 /就代表当前项目名~~~~  所以此处的效果同上~~~
        resource = Main.class.getResource("/com/fsx/maintest/demo.properties");
        System.out.println(resource); // 同上
        // 关于getResourceAsStream的使用,路径处理上和上面一致,此处就不做过多解释了
        InputStream resourceAsStream = Main.class.getResourceAsStream("demo.properties");
        System.out.println(resourceAsStream); //java.io.BufferedInputStream@33e5ccce
        // ==================最后如果你想直接加载Classpath类路径下的配置文件(此处以类路径下的spring.properties为例)===================
        // 这个/ 不能省略,否则classpathResource为null
        URL classpathResource = Main.class.getResource("/spring.properties");
        System.out.println(classpathResource); //file:/E:/work/remotegitcheckoutproject/myprojects/java/demo-war/target/classes/spring.properties
        System.out.println(Main.class.getResource("")); //.../demo-war/target/classes/com/fsx/maintest/ 它定位到的是Main这个类所在的路径
        System.out.println(Main.class.getResource("/")); //.../demo-war/target/classes/  它定位到的是类路径
    }


ClassLoader提供的获取资源


getSystemResource和getSystemResourceAsStream


这种方式也是我们平时非常常用的


    public static void main(String[] args) {
        // 这个大体上和class.getResource()类似。但是它没有class对路径的一个提前处理。所以它这里需要把路径写全了:com/fsx/maintest/demo.properties
        // 需要注意的是,因为它没有对路径处理的,所以不支持 `/`打头的这种绝对路径
        URL systemResource = ClassLoader.getSystemResource("com/fsx/maintest/demo.properties");
        System.out.println(systemResource); ///demo-war/target/classes/com/fsx/maintest/demo.properties
    //后面已经有一个/了,说明就是直接从classpath/目录下查找~~~~ 所以我们自己不需要再写/ 了
        systemResource = ClassLoader.getSystemResource("");
        System.out.println(systemResource); // .../demo-war/target/classes/  获取类路径的地址
        InputStream systemResourceAsStream = ClassLoader.getSystemResourceAsStream("com/fsx/maintest/demo.properties");
        System.out.println(systemResourceAsStream); //java.io.BufferedInputStream@46f5f779
        // 若要加载类路径下的文件,显然这个就更加的方便些~~~ 不需要 "/"了
        System.out.println(ClassLoader.getSystemResourceAsStream("spring.properties")); //java.io.BufferedInputStream@5a42bbf4
    }

备注像getSystemResources或者getResources相当于不仅在本类加载器中找,还会去父加载器中去找~~~~(一般目前而言,我们使用不着)


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


另外大多数情况下classLoader.getResources和ClassLoader.getSystemResources方法是等价的。比如下面两种方式是等价的:

// 实例方法
classLoader.getResources(META-INF/spring.factories)
// 静态方法
ClassLoader.getSystemResources(META-INF/spring.factories)


他俩的输出结果一样


File方式读取


    public static void main(String[] args) throws FileNotFoundException {
        // 显然通过这种间接的方式去构建一个File对象也是可行的 只是比较绕
        String filePath = ClassLoader.getSystemResource("spring.properties").getFile();
        System.out.println(new File(filePath).exists()); //true
  }


new File()路径名的两种方式


全路径名(带盘符的):

    public static void main(String[] args) {
        // 使用带盘符的绝对路径  显然这个把项目名以及target都暴露出来了
        File file = new File("E:\\work\\remotegitcheckoutproject\\myprojects\\java\\demo-war\\target\\classes\\com\\fsx\\maintest\\demo.properties");
        System.out.println(file.exists()); //true 这里是返回true,表示找到了这个文件
        // 另外一种方式,也可以这么写
        file = new File(System.getProperty("user.dir") + "\\target\\classes\\com\\fsx\\maintest\\demo.properties");
        System.out.println(file.exists()); // true
    }


相对路径名:

File的相对路径,请务必注意。它是相对于项目名的。也就是说和项目名平级的才能直接获取。


image.png


这个my.properties显然是是项目外层,和项目平级的,我们就可以这样直接获取:


    public static void main(String[] args) {
        // 采用相对路径   很显然,这里相对的是工程~~~
        File file = new File("my.properties");
        System.out.println(file.exists()); //true
    }


那么如果我们想要采用相对路径去获取工程内部的资源呢?


    public static void main(String[] args) {
        // 采用相对路径   很显然,这里相对的是工程~~~(这里也得把/demo-war/target/classes这些写出来,非常不优雅)
        File file = new File("../demo-war/target/classes/com/fsx/maintest/demo.properties");
        System.out.println(file.exists()); //true
    }


个人建议:一般都不太建议直接使用File,或者不建议直接new File()


JDK加载资源注意


不管是类对象的getResource()还是类加载器的getSystemResouce(),都是走的类加载器的getResource(),类加载器会搜索自己的加载路径来匹配寻找项。而最常用的类加载器就是AppClassLoader,又因为APPClassLoader的加载路径是classpath,所以网上文章一般都会说getClass().getResouce()是返回classpath,这是不够准确的。


整体来说,JDK提供的一些获取资源的方式,还是比较难用的。如果你处在Spring环境中,强烈建议使用它提供的资源访问接口,下面着重介绍

Spring提供的资源访问 Resource接口


它位于的包为org.springframework.core.io,属于Spring Framework的核心内容


可能很多用了Spring多年的程序员对Resource都了解有限,毕竟访问资源一般是搭建web工程框架的时候的事情。不过了解它也是非常有好处的。特别是你自己想基于Spring构建自己的框架的时候,就显得特别的有必要了~

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();
}


Resouce接口并不是一个根接口,它继承了一个简单的父接口 InputStreamSource,它只提供一个方法用以返回一个输入流:


InputStream getInputStream() throws IOException;


它还有如下几大分支:


image.png


文末会解释一下EncodedResource,至于Web中的MultipartFile,在Spring MVC相关篇章中会着重解释


image.png




相关文章
|
1月前
|
XML Java 数据格式
探索Spring之利剑:ApplicationContext接口
本文深入介绍了Spring框架中的核心接口ApplicationContext,解释了其作为应用容器的功能,包括事件发布、国际化支持等,并通过基于XML和注解的配置示例展示了如何使用ApplicationContext管理Bean实例。
86 6
|
1月前
|
XML 安全 Java
|
2月前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
78 1
|
30天前
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
44 1
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
42 1
|
2月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
42 0
|
Java Spring
通过Spring Resource接口获取资源(6)
通过Spring Resource接口获取资源
1135 0
|
Java Spring 数据格式
Spring Resource接口获取资源
1.1.1. Resource简介  在Spring内部实现机制,针对于资源文件(配置的xml文件)有一个统一的接口Resource。   1.1.1.1. 接口定义的方法 1.exists():判断资源文件是否存在。
1615 0
|
Java Spring 数据格式
通过Spring Resource接口获取资源
通过Spring Resource接口获取资源 目录 1       Resource简介 2       通过ResourceLoader获取资源 3       在bean中获取Resource的方式   1       Resource简介        在Spring内部,针对于资源文件有一个统一的接口Resource表示。
929 0