3个重要路径
在sun jdk中有上个重要的路径,这3个重要的路径分别是:
- sun.boot.class.path所代表的启动类路径
- java.ext.dirs所代表的扩展类路径
- 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的继承结构图:
其中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");Enumerationresources = classLoader.getResources("bean.xml");
一组是静态方法:
URL url = ClassLoader.getSystemResource("bean.xml");Enumerationresources = 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,结构如下:
在工程运行的时候会把上面的目录拷贝到一个包含在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文件):
那么我们可以通过下面的方式来获取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 { Enumerationresources = 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。