深刻了解 Java Resource && Spring Resource

Java中,为了从相对路径读取文件,常常会使用的方法即是:html

xxx.class.getResource();

xxx.class.getClassLoader().getResource();

Spring中,咱们还能够经过Spring提供的Resource进行一些操做:java

ClassPathResource

FileSystemResource

ServletContextResource

Resource template = ctx.getResource("some/resource/path/myTemplate.txt");

这里简单总结下他们的区别:web


ClassLoader##getResource()

这个方法是今天的主角。spring

咱们都知道ClassLoader的做用是用来加载.class文件的,而且ClassLoader是遵循Java类加载中的双亲委派机制的。api

那么,ClassLoader是如何找到这个.class文件的呢?答案是URLClassPath微信

Java中自带了3个ClassLoader分别是BootStrap ClassLoaderEtxClassLoader,AppClassLoader,app

这3个ClassLoader都继承自URLClassLoader,而URLClassLoader中包含一个URLClassPath用来记录每一个ClassLoader对应的加载.class文件的路径,当须要加载资源的时候,只管从URLClassPath对应的路径查找便可。webapp

下面是测试代码:ide

System.out.println("BootStrap ClassLoader ");
Stream.of(System.getProperty("sun.boot.class.path").split(";")).forEach(System.out::println);

System.out.println("ExtClassLoader:");  
Stream.of(System.getProperty("java.ext.dirs").split(";")).forEach(System.out::println);

System.out.println("AppClassLoader:");  
Stream.of(System.getProperty("java.class.path").split(";")).forEach(System.out::println);

输出以下:测试

BootStrap ClassLoader 

H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
H:\java\jdk1.8\jre\lib\sunrsasign.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\classes

ExtClassLoader:

H:\java\jdk1.8\jre\lib\ext
C:\Windows\Sun\Java\lib\ext

AppClassLoader:

H:\java\jdk1.8\jre\lib\charsets.jar
H:\java\jdk1.8\jre\lib\deploy.jar
H:\java\jdk1.8\jre\lib\ext\access-bridge-64.jar
H:\java\jdk1.8\jre\lib\ext\cldrdata.jar
H:\java\jdk1.8\jre\lib\ext\dnsns.jar
H:\java\jdk1.8\jre\lib\ext\jaccess.jar
H:\java\jdk1.8\jre\lib\ext\jfxrt.jar
H:\java\jdk1.8\jre\lib\ext\localedata.jar
H:\java\jdk1.8\jre\lib\ext\nashorn.jar
H:\java\jdk1.8\jre\lib\ext\sunec.jar
H:\java\jdk1.8\jre\lib\ext\sunjce_provider.jar
H:\java\jdk1.8\jre\lib\ext\sunmscapi.jar
H:\java\jdk1.8\jre\lib\ext\sunpkcs11.jar
H:\java\jdk1.8\jre\lib\ext\zipfs.jar
H:\java\jdk1.8\jre\lib\javaws.jar
H:\java\jdk1.8\jre\lib\jce.jar
H:\java\jdk1.8\jre\lib\jfr.jar
H:\java\jdk1.8\jre\lib\jfxswt.jar
H:\java\jdk1.8\jre\lib\jsse.jar
H:\java\jdk1.8\jre\lib\management-agent.jar
H:\java\jdk1.8\jre\lib\plugin.jar
H:\java\jdk1.8\jre\lib\resources.jar
H:\java\jdk1.8\jre\lib\rt.jar
F:\spring-test\target\classes

AppClassLoader负责经常使用的JDK jar以及项目所依赖的jar

上述参数能够经过 sun.misc.Launcher.class得到

经过输出的参数,咱们能够清晰的看出来各个ClassLoader负责的区域

说了这么多,这个和ClassLoader#getResource()有什么关系呢?

关系很大,前面刚刚提问过,ClassLoader是如何读取.class文件的呢?

答案是URLClassPath#getResource()方法:每一个UrlClassLoader都是经过URLClassPath来存储对应的加载区域,当须要查找.class文件的时候,就经过URLClassPath#getResource()查找便可。


下面再来看看ClassLoader#getResource()

//双亲委派查找   
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;
}

//因为BootStrap ClassLoader是C++写的,Java拿不到其引用。
//所以这里单独写了一个方法获取BootStrapResource()
private static URL getBootstrapResource(String name) {
    URLClassPath ucp = getBootstrapClassPath();
    Resource res = ucp.getResource(name);
    return res != null ? res.getURL() : null;
}

URLClassLoader#findResource()

public URL findResource(final String name) {

        URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(name, true);
                }
            }, acc);

        return url != null ? ucp.checkURL(url) : null;
    }

咱们只用注意这一句ucp.findResource(name, true);,这边是查找.class文件的方法,所以咱们能够总结出经过ClassLoader#getResource()的流程:

  • 首先,AppClassLoader委派给ExtClassLoader查找是否存在对应的资源
  • ExtClassLoader委派给BootStrap ClassLoader查找是有存在对应的资源
  • BootStrap ClassLoader经过URLClasspath查找本身加载的区域,查找到了即返回
  • BootStrap ClassLoader未查找到对应资源,ExtClassLoader经过URLClasspath查找本身加载的区域,查找到了即返回
  • ExtClassLoader未查找到对应资源,AppClassLoader经过URLClasspath查找本身加载的区域,查找到了即返回
  • AppClassLoader未查找到,抛出异常。

这个过程,就和加载.class文件的过程同样。

在这里咱们就能够发现,经过ClassLoader#getResource()能够获取JDK资源,所依赖的JAR包资源等

所以,咱们甚至能够这样写:

//读取java.lang.String.class的字节码

InputStream inputStream =Test.class.getClassLoader().getResourceAsStream("java/lang/String.class");
try(BufferedInputStream bufferedInputStream=new BufferedInputStream(inputStream)){
    byte[] bytes=new byte[1024];
    while (bufferedInputStream.read(bytes)>0){
        System.out.println(new String(bytes, StandardCharsets.UTF_8));
    }
}

明白了ClassLoader#getResource(),其实本篇文章就差很少了,由于后面要将的几个方法,底层都是ClassLoader#getResource()

class##getResource()

class##getResource()底层就是ClassLoader#getResource()

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

不过有个小区别就在于class#getResource()多了一个resolveName()方法:

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()大体就是判断路径是相对路径仍是绝对路径,若是是相对路径,则资源名会被加上当前项目的根路径:

Test.class.getResource("spring-config.xml");

resolve以后变成

com/dengchengchao/test/spring-config.xml

这样的资源就只能在当前项目中找到。

Test.class.getResource("test.txt");    //相对路径

Test.class.getResource("/");           //根路径

注意:ClassLoader#getResource()不能以/开头


Spring # ClassPathResource()

Spring中,对Resource进行了扩展,使得Resource可以适应更多的应用场景,

不过ClssPathResource()底层依然是ClassLoader##getResource(),所以ClassLoader##getResource()d的特性,ClassPathResource也支持

protected URL resolveURL() {
        if (this.clazz != null) {
            return this.clazz.getResource(this.path);
        } else {
            return this.classLoader != null ? this.classLoader.getResource(this.path) : ClassLoader.getSystemResource(this.path);
        }
    }

ClassPathResource用于读取classes目录文件

通常来讲,对于SpringBoot项目,打包后的项目结构以下:

xxx.jar

|--- BOOT-INF

|--------|--classes

|--------|----|--com

|--------|----|-- application.properties

|--------|----|--logback.xml

| -------|-- lib

|--- META-INF

|--- org

能够看到,ClassPathResource()的起始路径即是classes,平时咱们读取的application.properties即是使用ClasspathResource()获取的

在平时使用的过程当中,有三点须要注意:

  1. classpath 和 classpath* 区别:

    classpath:只会返回第一个查找到的文件
    classpath*:会返回全部查找到的文件

  2. Spring中,须要直接表示使用ClassPathResource()来查找的话,能够直接添加classpath:

  3. 使用classpath/和不以/开头没有区别


Spring # ServletContextResource

ServletContextResource是针对Servlet来作的,咱们知道,Servlet规定webapp目录以下:

image

ServletContextResource的路径则是xxx目录下为起点。也就是能够经过ServletContextResource获取到form.html等资源。

同时对比上面的ClassPathResource咱们能够发现:

"classpath:com"

等价于:

ServletContextResource("WEB-INF/classes/com")

Spring # FileSystemResource

FileSystemResource没什么好说的,就是系统目录资源,好比

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("D://test.xml");

它的标记头为file:

例如:

ApplicationContext ctx =
    new FileSystemXmlApplicationContext("flie:D://test.xml");

若是以为写得不错,欢迎关注微信公众号:逸游Java ,天天不定时发布一些有关Java进阶的文章,感谢关注

相关文章
相关标签/搜索