Class和ClassLoader关于getResource(),getResourceAsStream()的区别

文章目录


1 介绍

在java开发中,有一个关于文件获取的方式是必不可少的。比如,我们需要获取到对应的classpath路径下的一个文件,或者是我们在看spring源码的时候,经常看到ClassPathResource这个类
在这里插入图片描述
其内部的实现方式都是基于Class或者ClassLoader的getResource()进行获取的。

2 使用

我们会发现Class类和ClassLoader类下都是会存在这样的两个方法

public java.net.URL getResource(String name)
public InputStream getResourceAsStream(String name)

其对应的参数name:是关于资源的描述(资源路径或者资源名)name of the desired resource
其中,ClassLoader还存在以下几个常用的方法
在这里插入图片描述
下面四个方法是ClassLoader对应的静态方法

public static ClassLoader getSystemClassLoader()
public static InputStream getSystemResourceAsStream(String name)
public static Enumeration<URL> getSystemResources(String name)

我们同样可以通过ClassLoader的对应的静态方法获取当前类的类加载器

例如:

//1,我们通过当前类对象ClassPathResource获取对应的类加载器
ClassLoader classLoaderFromClass = ClassPathResource.class.getClassLoader();
//2,我们通过当前类加载器的静态方法获取对应的类加载器
ClassLoader classLoaderFromClassLoader = ClassLoader.getSystemClassLoader();

System.out.println(classLoaderFromClass == classLoaderFromClassLoader);//true

那么针对于Class和ClassLoader中的getResource()和getResourceAsStream之间的区别是什么呢?

  • ClassLoader.getResource(String name)只能够从classpath根目录开始匹配获取资源,写法Class.getResource(“test.properties”)
  • Class.getResource(String name)可以从当前Class所在包的路径开始匹配资源,写法;Class.getResource("")也可以从classpath根路径开始获取资源,写法:Class.getResource("/")

例如文件包项目:
在这里插入图片描述

//通过classLoader.getResource(String name) 获取对应classpath根目录中的文件
URL resourceFromClassLoaderResource = classLoaderFromClass.getResource("application.yaml");

//ClassLoader().getResource() 不能以 "/" 开头,且路径总是从 classpath 根路径开始;
//下面结果是null
URL resourceFromClassLoaderResourceByPre = classLoaderFromClass.getResource("/application.yaml");

//ClassLoader获取当前包对应的文件路径,test1.properties编译之后是会在com/zcswl/test 目录下
URLclassLoaderFromClassResource=classLoaderFromClass.getResource("com/zcswl/test/test1.properties");

//ClassLoader获取对应classpath根目录对应的META-INF信息
URL classLoaderFromClassResource1 = classLoaderFromClass.getResource("META-INF/spring.factories");

//通过Class.getResource("") ,获取对应classPath根路径下的文件
URL resourceFromClassResourceByPre = ClassPathResource.class.getResource("/application.yaml");

//Class.getResource获取当前ClassPathResource类的同级对应的test1.properties文件
URL resourceFromClassResource = ClassPathResource.class.getResource("test1.properties");

//Class.getResource("") 获取当前ClassPathResource不同目录的test.properties文件
URL resource = ClassPathResource.class.getResource("/com/zcswl/test/cglib/test2.properties");

getResourceAsStream()方法是获取对应路径文件的文件流,其文件路径匹配机制和其getResource(String name) 方法一样。

3 源码

首先我们分析一下Class.getResource(“”)相关的源码,通过上面的示例使用,对应Class的getResource(String name) 方法,即:

  • name不以‘/‘ 开头的,实际上是从当前类的字节码的路径中去搜索对应的文件,比如上面的文件包项目如果通过ClassPathResource的字节码获取对应的test1.properties,我们可以直接使用
//因为test1.properties和对应的ClassPathResource位于同一层目录,
URL test1URL = ClassPathResource.class.getResource("test1.properties");
  • 如果通过Class.getResource(String name)获取不同层的目录,或者根目录数据,我们需要在文件路径加上’/‘,例如,我们通过ClassPathResource字节码获取根目录中的application.yaml的文件,或者位于cglib目录下的test2.properties
//获取对应的根目录application.yaml文件
URL applicationURL = ClassPathResource.class.getResource("/application.yaml");

//获取对应的cglib目录下的test2.properties文件
URL test2URL = ClassPathResource.class.getResource("/com/zcswl/test/cglib/test2.properties");

进入到Class#getResource(String name)方法

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

我们会发现,首先类对于路径的字符进行了处理

/**
     * Add a package name prefix if the name is not absolute Remove leading "/"
     * if name is absolute
     * 如果名称不是绝对路径的话,添加包名称对应的前缀信息
     */
    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;
    }

该resolveName(String name)方法会将对应的不是绝对路径的名称增加对应的当前class字节码名称的name作为前缀,即

URL test1URL = ClassPathResource.class.getResource("test1.properties");

会经过resolveName变成 com/zcswl/test/test1.properties字符
在这里插入图片描述
然后再获取当前字节码对象的ClassLoader类加载器,再通过类加载器的ClassLoader.getResource(“”) 方法获取对应的URL
在这里插入图片描述
实际上,对于Class.getResource(String name) 方法而言,它的功能仅仅是对于非绝对路径的文件描述追加对应的Class.getName的前缀,(将对应的. 转换成 /)成一个新的文件描述,最终,都是通过ClassLoader.getResource() 方法获取

通过上面的示例,我们知道 ClassLoader.getResource(String name)其功能是只能从classpath根路径开始匹配资源 并且对应的文件描述不能含有对应的 ’/‘(斜杠)

public URL getResource(String name) {
        URL url;
        if (parent != null) {
            url = parent.getResource(name);
        } else {
            url = getBootstrapResource(name);
        }
        if (url == null) {
            url = findResource(name);
        }
        return url;
    }

首先,对应ClassLoader.getResource("")方法,该方法首先会从对应的父类加载器中查找对应的文件资源。如果url不存在,再由对应的子类加载器继续查询,(这一点和类加载机制的双亲委派类似)
在这里插入图片描述
即首先会从ExtClassLoader进行查找,
在这里插入图片描述
通过getBootstrapResource(String name) 从内置jvm加载器查找资源

发布了55 篇原创文章 · 获赞 14 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zcswl7961/article/details/103831231