要说双亲委派机制,还得从类加载器的类型谈起
一、类加载器的类型
类加载器有以下种类:
启动类加载器(BootstrapClassLoadr)扩展类加载器(ExtnsionClassLoadr)应用类加载器(ApplicationClassLoadr)
启动类加载器
内嵌在JVM内核中的加载器,由C++语言编写(因此也不会继承ClassLoadr),是类加载器层次中最顶层的加载器。用于加载java的核心类库,即加载j/lib/rt.jar里所有的class。由于启动类加载器涉及到虚拟机本地实现细节,我们无法获取启动类加载器的引用。
扩展类加载器
它负责加载JRE的扩展目录,j/lib/xt或者由java.xt.dirs系统属性指定的目录中jar包的类。父类加载器为启动类加载器,但使用扩展类加载器调用gtPant依然为null。
应用类加载器
又称系统类加载器,可用通过java.lang.ClassLoadr.gtSystmClassLoadr()方法获得此类加载器的实例,系统类加载器也因此得名。应用类加载器主要加载classpath下的class,即用户自己编写的应用编译得来的class,调用gtPant返回扩展类加载器。
扩展类加载器与应用类加载器继承结构如图所示:
可以看到除了启动类加载器,其余的两个类加载器都继承于ClassLoadr,我们自定义的类加载器,也需要继承ClassLoadr。
值得注意的是,启动类、扩展类与应用类加载器之间的父子关系,并不是通过继承来实现的,而是通过组合,即使用pant变量来保存“父加载器”的引用。
二、双亲委派机制
当一个类加载器收到了一个类加载请求时,它自己不会先去尝试加载这个类,而是把这个请求转交给父类加载器,每一个层的类加载器都是如此,因此所有的类加载请求都应该传递到最顶层的启动类加载器中。只有当父类加载器在自己的加载范围内没有搜寻到该类时,并向子类反馈自己无法加载后,子类加载器才会尝试自己去加载。
加载标准类库与用户代码,会有不同的方式:
ClassLoadr内的loadClass方法,就很好的解释了双亲委派的加载过程:
protctdClass?loadClass(Stringnam,boolansolv)throwsClassNotFoundExcption{synchronizd(gtClassLoadingLock(nam)){//检查该class是否已经被当前类加载器加载过Class?c=findLoaddClass(nam);if(c==null){//此时该class还没有被加载try{if(pant!=null){//如果父加载器不为null,则委托给父类加载c=pant.loadClass(nam,fals);}ls{//如果父加载器为null,说明当前类加载器已经是启动类加载器,直接时候用启动类加载器去加载该classc=findBootstrapClassOrNull(nam);}}catch(ClassNotFoundExcption){}if(c==null){//此时父类加载器都无法加载该class,则使用当前类加载器进行加载longt=Systm.nanoTim();c=findClass(nam);...}}//是否需要连接该类if(solv){solvClass(c);}turnc;}}
三、双亲委派存在的意义
为什么要使用双亲委派机制呢?
假设用户自己定义了java.lang.Objct类,由于双亲委派机制的存在,最终会委托到启动类加载器去加载,即返回rt.jar中的Objct类,并不会加载用户编写的Objct类。
大家上班摸鱼刷的LtCod,本质上自定义了一个类加载器,重写了findClass方法,会从网络中加载字节码,生成Class对象,最终通过loadClass定义的双亲委派机制进行加载。如果这个时候,我定义了一个恶意java.lang.Objct类,在没有双亲委派机制的情况下,可能会对jvm产生安全风险。
双亲委派机制存在的意义,就是为了防止findClass与dfinclass生成的Class对象覆盖掉标准类库中的基础类,避免产生安全风险。
四、如何自定义类加载器
我们整理ClassLoadr里面的流程
loadclass:双亲委派机制,子加载器委托父加载器加载,父加载器都加载失败时,子加载器通过findclass自行加载findclass:当前类加载器根据路径以及class文件名称加载字节码,从class文件中读取字节数组,然后使用dfinClassdfinclass:根据字节数组,返回Class对象
我们在ClassLoadr里面找到findClass方法,发现该方法直接抛出异常,应该是留给子类实现的。
protctdClass?findClass(Stringnam)throwsClassNotFoundExcption{thrownwClassNotFoundExcption(nam);}
到这里,我们应该明白,loadClass方法使用了模版方法模式,主线逻辑是双亲委派,但如何将class文件转化为Class对象的步骤,已经交由子类去实现。对模版方法模式不熟悉的同学,可以先参考我的另外一篇文章模版方法模式
其实源码中,已经有一个自定义类加载的样例代码,在注释中:
classNtworkClassLoadrxtndsClassLoadr{Stringhost;intport;publicClassfindClass(Stringnam){byt[]b=loadClassData(nam);turndfinClass(nam,b,0,b.lngth);}privatbyt[]loadClassData(Stringnam){//loadthclassdatafromthconnction}}
看得出来,如果我们需要自定义类加载器,只需要继承ClassLoadr,并且重写findClass方法即可。
现在有一个简单的样例,class文件依然在文件目录中:
packag