【小家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




相关文章
|
4天前
|
Java 开发者 Spring
Spring项目中Ordered接口的应用:全局过滤器(GlobalFilter)的顺序控制
Spring项目中Ordered接口的应用:全局过滤器(GlobalFilter)的顺序控制
11 2
|
4天前
|
Dubbo Java 应用服务中间件
Spring Boot 调用 Dubbo 接口与编写 Dubbo 接口实战
Spring Boot 调用 Dubbo 接口与编写 Dubbo 接口实战
23 1
|
6天前
|
前端开发 Java Spring
spring接口版本控制方案及RequestMappingHandlerMapping接口介绍
spring接口版本控制方案及RequestMappingHandlerMapping接口介绍
|
6天前
|
缓存 前端开发 Java
Spring Boot中防止接口重复提交
Spring Boot中防止接口重复提交
|
9月前
|
XML Java 数据格式
深入理解Spring中的Resource资源管理
深入理解Spring中的Resource资源管理
98 0
|
Java Spring
Spring Context 资源管理
Spring Context 资源管理
|
Java Spring 容器
Spring 资源管理 (Resource)
Spring 为什么引入资源管理? Java 中有各种各样的资源,资源的位置包括本地文件系统、网络、类路径等,资源的形式可以包括文件、二进制流、字节流等,针对不同的资源又有不同的加载形式。
88 0
Spring 资源管理 (Resource)
|
XML Java 数据格式
Spring IoC资源管理之ResourceLoader
Spring IoC资源管理之ResourceLoader
189 0
Spring IoC资源管理之ResourceLoader
|
XML 算法 Java
Spring IoC资源管理之Resource
Spring IoC资源管理之Resource
202 0
Spring IoC资源管理之Resource
|
Java Spring
Spring的资源管理
Spring 提供了简单的接口来管理资源,并支持多种资源类型。
174 0