博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用ClassLoader加载资源详解
阅读量:6228 次
发布时间:2019-06-21

本文共 9197 字,大约阅读时间需要 30 分钟。

hot3.png

3个重要路径

在sun jdk中有上个重要的路径,这3个重要的路径分别是:

  1. sun.boot.class.path所代表的启动类路径
  2. java.ext.dirs所代表的扩展类路径
  3. java.class.path所代表的应用类路径

这3个路径代表者java搜索资源的3级路径,其实是与类加载是对应的。类其实一般就是加载class文件资源,类加载搜索的class文件资源也是按照这3级路径来的。

这3个路径可以通过如下的方式获取:

public void printResourcePath(){    Properties ps = System.getProperties();    System.out.println(ps.getProperty("sun.boot.class.path"));    System.out.println(ps.getProperty("java.ext.dirs"));    System.out.println(ps.getProperty("java.class.path"));}

3个重要的ClassLoader

先来看一张ClassLoader的继承结构图:

ClassLoaderHierarchy

其中sun.misc.Launcher$AppClassLoader和sun.misc.Launcher$ExtClassLoader的父类都是java.net.URLClassLoader。

但是有一个逻辑上的3级父级关系就是sun.misc.Launcher$AppClassLoader父级是sun.misc.Launcher$ExtClassLoader,sun.misc.Launcher$ExtClassLoader的父级是BootstrapClassLoader,其中BootstrapClassLoader不是使用Java代码实现的,注意他们是父级关系不是继承关系,只是搜索资源和类加载加载的层级关系。

结论

这里我们把总结的结论放在前面,分析放在后面。ClassLoader中有2组资源搜索的接口 (结论的ClassLoader是以sun.misc.Launcher$AppClassLoader,并且没有修改过sun.misc.Launcher$AppClassLoader,sun.misc.Launcher$ExtClassLoader,BootstrapClassLoader3个类加载器的逻辑父级关系为前提) 一组是实例方法:

URL url = classLoader.getResource("xx/bean.xml");Enumeration
resources = classLoader.getResources("bean.xml");

一组是静态方法:

URL url = ClassLoader.getSystemResource("bean.xml");Enumeration
resources = ClassLoader.getSystemResources("bean.xml");

对于这2组方法的区别,是基本没有区别,这个后面分析。不管是实例方法还是静态方法都是按照下面的模式查找:

getResource会先从sun.boot.class.path解析出的路径集合中开始查找,然后从java.class.path解析出的路径集合中开始查找,找到一个就结束。

getResources会找出sun.boot.class.path和java.class.path解析出的路径集合中所有名称为name的资源。

注意:这里有2中资源,一种是文件,一种是jar包中的文件。我们可以从sun.boot.class.path,java.class.path,java.ext.dirs解析出的路径看出分为2类,一类是目录,一类是jar包。

对于目录和jar包底层处理方式是不同的,目录是通过sun.misc.URLClassPath的内部类FileLoader处理的,jar包是通过sun.misc.URLClassPath的内部类JarLoader处理的。有兴趣的同学可以自己看一下源码。

其实对于我们使用可以把这2类看做一类:就是sun.boot.class.path,java.class.path,java.ext.dirs解析出的路径作为base路径+name相对路径。

如果对上面的结论表达还不够清晰,请用下面的测试实例测试一下就会非常清楚了。

顺便赠送一个Class#getResource(String name) 其实这个就是使用的ClassLoader的getResource,稍作处理的是name以"/"开头就和ClassLoader完全一样,如果name不是以"/"开头就是name+Class的所在的包名。

测试实例

代码放在后面一点啊,先简单的介绍一下: 我的sun.boot.class.path包含一个C:\Program Files\Java\jre7\classes路径,在这个路径下的目录结构如下:

bean.xml

xx/bean.xml

在我的工程目录下有一个资源文件其中也包含一个bean.xml,结构如下:

Resource

在工程运行的时候会把上面的目录拷贝到一个包含在java.class.path所代表路径集合中的一个目录下。

我们通过getResource("bean.xml")找到的就是C:\Program Files\Java\jre7\classes\bean.xml文件。而不是工程目录下的bean.xml文件。

如果我们要找C:\Program Files\Java\jre7\classes\xx\bean.xml文件就要加上相对路径,使用如下的方式:

getResource("xx/bean.xml")

如果要找jar包中的文件,也要加上相对路径。就是把jar解压之后的路径。例如我们把rt.jar包解压,Class.java所在的路径结构如下(这个是源码包,实际是rt包是class文件):

rt

那么我们可以通过下面的方式来获取Class.class这个资源:

getResource("java/lang/Class.class");

其实上面的方式就是常用的类加载的方式。

import java.io.IOException;import java.net.URL;import java.util.Enumeration;import java.util.Properties;import org.junit.Before;import org.junit.Test;public class ClassLoaderResourceTest {        @Before    public void printResourcePath(){        Properties ps = System.getProperties();        System.out.println("--------------");        System.out.println(ps.getProperty("sun.boot.class.path"));        System.out.println(ps.getProperty("java.ext.dirs"));        System.out.println(ps.getProperty("java.class.path"));        System.out.println("--------------");    }        @Test    public void testClassLoaderResource(){        ClassLoader classLoader = this.getClass().getClassLoader();        printClassLoader(classLoader);        URL url = classLoader.getResource("xx/bean.xml");        System.out.println(url);        url = classLoader.getResource("bean.xml");        System.out.println(url);        url = classLoader.getResource("java/lang/Class.class");        System.out.println(url);        url = classLoader.getResource("Class.class");        System.out.println(url);    }        @Test    public void testGetResources(){        ClassLoader classLoader = this.getClass().getClassLoader();        try {            Enumeration
resources = classLoader.getResources("bean.xml"); while(resources.hasMoreElements()){ System.out.println(resources.nextElement()); } } catch (IOException e) { e.printStackTrace(); } } @Test public void testGetSystemResources(){ try { Enumeration
resources = ClassLoader.getSystemResources("bean.xml"); while(resources.hasMoreElements()){ System.out.println(resources.nextElement()); } } catch (IOException e) { e.printStackTrace(); } } @Test public void testGetSystemResource(){ URL url = ClassLoader.getSystemResource("bean.xml"); System.out.println(url); } @Test public void testClassGetResource(){ URL url = this.getClass().getResource("/bean.xml"); System.out.println(url); } private void printClassLoader(ClassLoader classLoader){ do{ System.out.println(classLoader); classLoader = classLoader.getParent(); }while(classLoader != null); }}

分析

这里我们分析一个实例方法ClassLoader#getResource,其他3个方法基本一样。

@Test    public void testClassLoaderResource(){        ClassLoader classLoader = this.getClass().getClassLoader();        url = classLoader.getResource("bean.xml");        System.out.println(url);    }

我们没有重写ClassLoader,所以上面的测试代码的classLoader是sun.misc.Launcher$AppClassLoader类的实例。我们从sun.misc.Launcher$AppClassLoader继承体系中查找在哪一个类中实现了getResource方法,非常容易的找到就是在ClassLoader类中,下面我们看一下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;    }

代码看上去非常简单,但是非常绕。我们指定这个方法是sun.misc.Launcher$AppClassLoader从ClassLoader中继承的。sun.misc.Launcher$AppClassLoader的逻辑父级parent是sun.misc.Launcher$ExtClassLoader,sun.misc.Launcher$ExtClassLoader的逻辑父级是BootstrapClassLoader,而实际上sun.misc.Launcher$ExtClassLoader被构造的时候设置的逻辑父级parent是null。

关于AppClassLoader的构造可以查看Launcher$AppClassLoader#getAppClassLoader方法。 关于ExtClassLoader的构造可以查看Launcher$ExtClassLoader#getExtClassLoader方法。

现在上面的代码的逻辑层次就清晰一些了AppClassLoader找到了ExtClassLoader,ExtClassLoader的parent是null,就使用getBootstrapResource(name)获取url,下面我们看一下getBootstrapResource方法。

private static URL getBootstrapResource(String name) {        URLClassPath ucp = getBootstrapClassPath();        Resource res = ucp.getResource(name);        return res != null ? res.getURL() : null;    }
static URLClassPath getBootstrapClassPath() {        return sun.misc.Launcher.getBootstrapClassPath();    }
public static URLClassPath getBootstrapClassPath() {        return BootClassPathHolder.bcp;    }

BootClassPathHolder是Launcher的内部静态类,就是完成了bcp的初始化,这里就不列举代码了,就说一下是bcp什么。BootClassPathHolder.bcp就是把sun.boot.class.path所代表的路径集合转化为urls然后构造了一个URLClassPath。

所以接下来看一下URLClassPath的getResource方法: URLClassPath#getResource

public Resource getResource(String name) {        return getResource(name, true);    }

URLClassPath#getResource

public Resource getResource(String name, boolean check) {        if (DEBUG) {            System.err.println("URLClassPath.getResource(\"" + name + "\")");        }        Loader loader;        for (int i = 0; (loader = getLoader(i)) != null; i++) {            Resource res = loader.getResource(name, check);            if (res != null) {                return res;            }        }        return null;    }

这里我们不跟getLoader的代码,只需要知道有3个Loader,一个是Loader,一个是FileLoader,一个是JarLoader。这3个Loader就是为了处理Resource的。getLoader就是通过一个url(从URLClassPath获取的urls中获取,也就是构造bcp从sun.boot.class.path转化而来的urls)获取一个处理这个url代表的Resource的Loader。

下面我们看一点Loader的getResource方法。

Resource getResource(final String name, boolean check) {            final URL url;            try {                url = new URL(base, ParseUtil.encodePath(name, false));            } catch (MalformedURLException e) {                throw new IllegalArgumentException("name");            }            final URLConnection uc;            try {                if (check) {                    URLClassPath.check(url);                }                uc = url.openConnection();                InputStream in = uc.getInputStream();                if (uc instanceof JarURLConnection) {                    /* Need to remember the jar file so it can be closed                     * in a hurry.                     */                    JarURLConnection juc = (JarURLConnection)uc;                    jarfile = JarLoader.checkJar(juc.getJarFile());                }            } catch (Exception e) {                return null;            }            return new Resource() {                public String getName() { return name; }                public URL getURL() { return url; }                public URL getCodeSourceURL() { return base; }                public InputStream getInputStream() throws IOException {                    return uc.getInputStream();                }                public int getContentLength() throws IOException {                    return uc.getContentLength();                }            };        }

其实上面的代码已经和资源的搜索没有什么关系了,只是获取Resource的最底层的方法。每一个URL都一个来处理流的URLStreamHandler。

转载于:https://my.oschina.net/u/2474629/blog/1501101

你可能感兴趣的文章
VS2010几款超赞的扩展辅助工具总结
查看>>
Tomcat embed
查看>>
Asp.Net Web API 2第六课——Web API路由和动作选择
查看>>
如何使用seajs+jQuery构建中型项目
查看>>
js html5推送 实例
查看>>
ASP.NET div信息提示框显示几秒后隐藏
查看>>
常用的正则表达式C#工具类
查看>>
IOS适配
查看>>
WhyDX9:翻写D3D红龙书中的程序
查看>>
RFC 4627 JSON
查看>>
UML类图
查看>>
Flex父子窗体相互调用
查看>>
AP_应付模组在月结的处理
查看>>
javascript如何判断访问网页的设备及是否支持触屏功能
查看>>
MFC 虚函数与消息映射区别
查看>>
每日一小练——列出全部子集
查看>>
[再寄小读者之数学篇](2014-06-23 Bernstein's inequality)
查看>>
微信公众平台开发(98) UnionID
查看>>
《CLR via C#》读书笔记 之 线程基础
查看>>
Linux中的lo回环接口详细介绍
查看>>