zoukankan      html  css  js  c++  java
  • 深入了解 Java Resource && Spring Resource

    摘自:https://www.cnblogs.com/dengchengchao/p/11836916.html

    深入了解 Java Resource && Spring Resource

     

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

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

    Spring中,我们还可以通过Spring提供的Resource进行一些操作:

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

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


    ClassLoader##getResource()

    这个方法是今天的主角。

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

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

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

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

    下面是测试代码:

    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:javajdk1.8jrelib
    esources.jar
    H:javajdk1.8jrelib
    t.jar
    H:javajdk1.8jrelibsunrsasign.jar
    H:javajdk1.8jrelibjsse.jar
    H:javajdk1.8jrelibjce.jar
    H:javajdk1.8jrelibcharsets.jar
    H:javajdk1.8jrelibjfr.jar
    H:javajdk1.8jreclasses
    
    ExtClassLoader:
    
    H:javajdk1.8jrelibext
    C:WindowsSunJavalibext
    
    AppClassLoader:
    
    H:javajdk1.8jrelibcharsets.jar
    H:javajdk1.8jrelibdeploy.jar
    H:javajdk1.8jrelibextaccess-bridge-64.jar
    H:javajdk1.8jrelibextcldrdata.jar
    H:javajdk1.8jrelibextdnsns.jar
    H:javajdk1.8jrelibextjaccess.jar
    H:javajdk1.8jrelibextjfxrt.jar
    H:javajdk1.8jrelibextlocaledata.jar
    H:javajdk1.8jrelibext
    ashorn.jar
    H:javajdk1.8jrelibextsunec.jar
    H:javajdk1.8jrelibextsunjce_provider.jar
    H:javajdk1.8jrelibextsunmscapi.jar
    H:javajdk1.8jrelibextsunpkcs11.jar
    H:javajdk1.8jrelibextzipfs.jar
    H:javajdk1.8jrelibjavaws.jar
    H:javajdk1.8jrelibjce.jar
    H:javajdk1.8jrelibjfr.jar
    H:javajdk1.8jrelibjfxswt.jar
    H:javajdk1.8jrelibjsse.jar
    H:javajdk1.8jrelibmanagement-agent.jar
    H:javajdk1.8jrelibplugin.jar
    H:javajdk1.8jrelib
    esources.jar
    H:javajdk1.8jrelib
    t.jar
    F:spring-test	argetclasses

    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进阶的文章,感谢关注

     
     
    分类: 技术精选
  • 相关阅读:
    SpringBoot集成Mybatis
    springboot通过slf4j配置日志
    SpringBoot导入jsp依赖始终报错
    shiro小记
    阿里开发手册华山版——(编程规约篇)记录目前自己不合理的地方
    [网络流24题] 2. 太空飞行计划问题 解题报告
    LOJ 6089 小 Y 的背包计数问题 解题报告 (动态规划)
    UVA 10599 Blocks 解题报告 (动态规划)
    Comet OJ#12E Ternary String Counting 解题报告
    [WC2016]挑战NPC 解题报告
  • 原文地址:https://www.cnblogs.com/xichji/p/11839843.html
Copyright © 2011-2022 走看看