----------- android培训、java培训、期待与您交流! ------------
类加载器及其委托机制的深入分析
1.类加载器和类加载器的作用:
类加载器概念:加载类的工具
在java程序里面用到一个类,出现了类的名字,java虚拟机首先把类的字节码加载到内存,通常这个字节码的原始信息放在硬盘上classpath指定目录下,.class文件的内容加载到硬盘中来再进行一些处理,处理完的结果就是字节码。
类加载器的作用:
把.class文件从硬盘上加载进来,再对它进行一些处理,这些工作都是由类加载器在做。
只要用到某个类,就要通过类加载器把它的二进制加进内存
2.java虚拟机可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
3.类加载器也是JAVA类,因为其他是java类的类加载器本身也要被类加载器加载,所以必须要有一个类加载器不是java类,这正是BootStrap.【嵌套在java虚拟机内核中,当java虚拟启动时,它已经存在在里面】
创建一个java类ClassLoaderTest,在main方法中写以下语句:
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
找到此类的加载器为AppClassLoader;
System.out.println(System.class.getClassLoader().getClass().getName());
抛出空指针,因为System.class.getClassLoader()返回值为null,说明此类加载器为BootStrap
4.JAVA虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级类加载器对象或者默认采用系统类加载器为其父级类加载器。
用以下程序在main中验证,用到刚才创建的ClassLoaderTest.java
ClassLoaderloader=ClassLoaderTest.getClass().getClassLoader(); while(loader!=null){ System.out.println(loader.getClass().getName());//获得加载器的名称 loader=loader.getParent();//获得此加载器的父级类加载器 } System.out.println(loader);
按子父关系打印出三个加载器
将ClassLoaderTest.java打成jar包放入jre/lib/ext目录下,运行程序看出ExtClassLoader是专门加载ext目录下的jar包
自定义类加载器:
必须继承ClassLoader,每个类加载器都必须有父亲,才能保证挂到虚拟机的类加载器树上
类加载器的委托机制
当java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
l 首先当前线程的类加载器去加载线程中的第一个类【java.lang.Thread类中的setContextClassLoader(ClassLoader c1)】
l 如果类A中引用了类B,JAVA虚拟机将使用加载A的类装载器来加载B
l 还可以直接调用自定义加载器类的对象.loadClass()来指定类加载器去加载某个类
每个类加载器加载类时,又先委托给其上级类加载器.
每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托父类直至BootStrap。当BootStrap也无法加载当前的类时,才一级级回退到子孙类装载器去进行真正的加载。当回退到发起者类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
类加载采用委托机制,这样可以保证爸爸们优先,也就是总是先使用爸爸们能找到的类。
自定义类加载器的编写原理分析
1.自定义的类加载器必须继承ClassLoader
2.该类中的方法Class<?> loadClass(Stringname):指定一个类名让其加载,返回Class
自定义的类加载器要不要重写loadClass()?--》不用,因为loadClass内部会先找它的爸爸,当爸爸返回以后,再接着调用protectedClass<?> findClass(String name),我们只需要覆盖findClass()就可以了,那么自定义的类加载器就有另一套机制(爸爸的,自己的)
模板方法设计模式:父类里面拥有loadClass()方法,不管哪个子类它们的loadClass过程是一样的【找父类,父类做不了返回来自己做】,但是每个子类自己干的代码不一样,而子类1和子类2找爸爸的流程是一样的,那么就把此流程代码放在父类里面编写,所有的子类都继承父类的此方法。如果父类不会做,就把此流程定义为抽象的,让子类填充。总体的流程在父类中已规定好,流程的细节父类无法确定,就抽象出来交给子类完成
此处根据需求应该覆盖findClass(),是为了保留loadClass()里的流程,保证自己只需要实现局部细节,如果覆盖了loadClass(),流程就没有了。当得到class文件里的二进制数据,怎样把class文件的内容转换成字节码?由Class<?>definClass(byte[] b,int off,int len)完成
编写对class文件进行加密的工具类
1.创建一个MyClassLoader.java既作为类加载器也作为加密工具,其代码块如下
public static void main(String[] args) throws Exception{ String srcPath=args[0]; String destDir=args[1]; FileInputStream in=new FileInputStream(srcPath); destFileName=srcPath.substring(srcPath.lastIndexOf("\\")+1); destPath=destDir+"\\"+destFileName; FileOutputStream out=newFileOutputStream(destPath); cypher(in,out); in.close(); out.close(); } public static void cypher(InputStream in,OutputStreamout) throws IOException{ int b; while((b=in.read())!=-1){ out.write(b ^ 0xff); } }
2.创建一个被程序调用去加密的java类ClassLoaderAttachment.java,与上面创建的同包
public class ClassLoaderAttachment extendsjava.util.Date{ public String toString(){ return "hello,world"; } }
3.对该class文件进行加密处理,要求加密完后的结果(.class文件)放入文件夹itcastlib中,操作如下:复制ClassLoaderAttachment.class的绝对路径-->运行MyClassLoader【Run as--Open Run Dialog,Arguments中填入ClassLoaderAttachment.class的绝对路径和目标地址itcastlib-->Main中的类为MyClassLoader】运行完以后,实现了要求,即.class在itcastlib文件夹中存在
4.将加密后的.class文件替换原来正常产生的ClassLoaderAttachment.class,另一个类ClassLoaderTest.java的main方法中执行newClassLoaderAttachment().toString()抛出异常,所以接下来要对加密的ClassLoaderAttachment.class进行解密
编写和测试自己编写的解密类加载器
1.将普通类MyClassLoader.java变成一个类加载器,继承抽象类ClassLoader
2.覆盖findClass(String name),补充如下代码:
private String classDir;//需要解密的.class文件所在目录 @override protected class<?> findClass(String name)throws ClassNotFoundException{ StringclassFileName=classDir+"\\"+name+".class";//要解密的.class文件绝对路径 try{ FileInputStream fis=newFileInputStream(classFileName); ByteArrayOutputStream bos=newByteArrayOutputStream(); cypher(fis,bos); fis.close(); byte[] bytes=bos.toByteArray(); returndefineClass(bytes,0,bytes.length); }catch(Exception e){ } return null; } public MyClassLoader(){} public MyClassLoader(String classDir){ this.classDir=classDir; }
3.调用自定义的类加载器,此处在ClassLoaderTest.java的main方法中【先把工程中原始代码编译的ClassLoaderAttachment.class删除,以便自定义类加载器的父类找不到此class,创造只能被自定义的类加载器加载的环境】
/* new MyClassLoader()默认挂到系统的类加载器上作为孩子,当自定义对象加载ClassLoaderAttachment的字节码成功以后,用字节码创建一个对象并打印 */ Class clazz=newMyClassLoader("itcastlib").loadClass("ClassLoaderAttachment"); Date d1=(Date)clazz.newInstance(); System.out.println(d1);
正常打印出结果hello,world,走的自定义加载器
将itcastlib中的.class文件拷出还原到对应目录下,这样程序就调用父类的类加载器,前提是.class文件必须带包名才能得到正确结果,形如loadClass("cn.itcast.day2.ClassLoaderAttachment ");【父类只能加载带包名的.class文件】
拿出带包名的class文件的名字:name.substring(name.lastIndexOf(".")+1);