我们正常遇到这样的情况,自定义一个jar文件中包含一个class【类】文件。我们想重写这个类的话直接在工程中包名及类名一致就可行。
但是对于某些类文件例如String等一些类,在我们的工程中直接重写这些类,是调用不了的。这是什么原因呢?
这和java的类的加载机制有关,那到底java是怎么加载类文件?这个问题也是下文将阐述的问题。
首选要提到的是ClassLoader,那什么是ClassLoader?
ClassLoader负责将相应的类文件(即class文件)装载到内存中。jvm中存在多种类型的ClassLoader,主要分为4类:
(1)BootStrap[启动类加载器] 是最顶层的类加载器,它是由C++编写而成,并且已经内嵌到JVM中了,主要用来读取Java的核心类库JRE/lib/rt.jar、resources.jar、charsets.jar 等
(2)Extension ClassLoader[扩展类加载器] 是用来读取Java的扩展类库,读取JRE/lib/ext/*.jar
(3)App ClassLoader[应用类加载器] 是用来读取当前应用下CLASSPATH指定的所有jar包或目录的类文件【是用户自定义类装载器的缺省父装载器】。
(4)Custom ClassLoader[用户自定义类加载器] 是用户自定义编写的,它用来读取指定类文件
不同的类装载器,只能加载相应范围的类,哪怕是同包下的类,只要他们不属于同一类装载器,都是相互隔绝的。这对一些有安全隐患的类起到了安全隔离的作用。使它不能冒充系统类来破坏程序正常运作。
这些ClassLoader组成了jvm的ClassLoader的体系结构,如下图所示为。
从图中我们可以看出,当加载一个类的时候,是先检查是当前加载器是否已经加载该类,如已加载则返回。否则委托给它的父加载器检查是否已经加载,一直检查到BootStrap【启动类加载器】,如果还是没有发现该类被加载,则BootStrap【启动类加载器】尝试加载该类,加载成功则返回,如没有加载到则将委托还给子装载器,子类加载器继续加载,一直装载到最底层的ClassLoader【可能为App ClassLoader或者Custom ClassLoader,甚至可能为Extension ClassLoader】,如果没有装载到相应的类则抛出ClassNotFoundException。整个模型也称作双亲委派模型。
总结来说 双亲委派方式的类加载,指的是优先从顶层启动类加载器开始,自顶向下的方式加载类的模型 (参见图示)。
可能有点迷糊,我们结合一个实际的源码来说明该模型。
java中的Launcher类【在sun.misc包下】,它包含两个内部类AppClassLoader、ExtClassLoader,java通过这两个类完成对应用及扩展类文件的加载,它们继承URLClassLoader类,而URLClassLoader类继SecureClassLoader类,SecureClassLoader继承抽象类ClassLoader。
它们的类图关系如下
虽然AppClassLoader、ExtClassLoader都是继承于URLClassLoader,但是AppClassLoader的父加载器为ExtClassLoader【这里就不多讲了,可以看看源码就知道了】。而对于AppClassLoader、ExtClassLoader类中并未覆盖抽象类ClassLoader的loadClass方法。ClassLoader的loadClass的方法如下
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 首选查看类是否已经被加载 Class c = findLoadedClass(name); //没有被加载 if (c == null) { try { //如果父加载器不为空,父加载器查看该类是否被加载 if (parent != null) { c = parent.loadClass(name, false); } else { //如果父加载器不存在调用BootStrap查看是否已被加载 c = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. //如果顶级装载器BootStrap未发现已加载该类,增尝试加载该类 c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
而findBootstrapClass0方法如下
private Class findBootstrapClass0(String name) throws ClassNotFoundException { check(); if (!checkName(name)) throw new ClassNotFoundException(name); return findBootstrapClass(name); } private native Class findBootstrapClass(String name) throws ClassNotFoundException;
它是一个本地接口的方法时C++写的。
从源码中可以看出ClassLoader总是现从下往上查看类是否已被加载,然后从上往下尝试加载类。
一般自定类加载器时loadClass是不重写,而自定义加载器重写的是findClass。从源码中我们可以看出,所有加载器的顶级加载器为BootStrap【即父加载器不存时】,同时注意到的时出于安全等因素考虑, BootStrap不会加载 lib 存在的陌生类或jar , 开发者通过将要加载的非 JDK 自身的类放置到此目录下期待启动类加载器加载是不可能的,其他类加载器无这样的限制 。
示例代码如下
public class classLoaderTest { public static void main( String args[] ){ classLoaderTest t = new classLoaderTest(); System.out.print( t.getClass() ); System.out.println( t.getClass().getClassLoader() ) ; System.out.println( t.getClass().getClassLoader().getParent() ) ; System.out.println( t.getClass().getClassLoader().getParent().getParent() ) ; } }
类加载器查看如下
class classLoaderTest sun.misc.Launcher$AppClassLoader@82ba41 sun.misc.Launcher$ExtClassLoader@923e30 null
第二行的结果表示 classLoaderTest 的类的加载器为 AppClassLoader
第三行的结果表示 AppClassLoader的父加载器为 ExtClassLoader
第四行的结果null表示 ExtClassLoader 的父加载器为 BootStrap
整个的加载流程如下自定类classLoaderTest,创建该类对象时,先是AppClassLoader检查是否已经加载该类,AppClassLoader并未加载该类,AppClassLoader委托给ExtClassLoader,而ExtClassLoader也未发现已经装载该类ExtClassLoader将委托交给BootStrap,BootStrap也未发现。这时BootStrap将尝试加载classLoaderTest,BootStrap未加载到,将委托交还给ExtClassLoader,ExtClassLoader未加载到,将委托交还给AppClassLoader,AppClassLoader加载到该类,并创建classLoaderTest。
这里我们回到问题为什么对于String等类,在应用重写,为什么不能覆盖系统的String类型。
从双亲委派模型 中我们可以知道,String对象存在rt.jar中它是由BootStrap负责加载,这样我们在应用中重写该类的时候无法加载自定义的String类【且是永远加载不到】,只能加载系统的String,这里也是java沙箱模型的第一保障。
备注:
用java –verbose:class ClassLoaderTest
可以查看具体的类运行的时候类加载过程
图及部分类容参考
http://www.cnblogs.com/ChrisWang/archive/2009/11/17/Inside-JVM-4-ClassLoader-Knowledge-Sharing.html