之前在一次面试中有被问道类加载器的问题,当时没答出来。今天既然看到张老师讲类加载器的视频,就写一篇笔记吧。
1.类加载器及其委托机制的深入分析:
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类加载器负责加载特定位置的类: BootStrap , ExtClassLoader , AppClassLoader。
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有类加载器采用具有父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其指定一个父级加载器对象或者默认采用系统类加载器为其父级类加载器。
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println(System.class.getClassLoader()); ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while (loader != null) { System.out.println(loader.getClass().getName()); loader = loader.getParent(); } System.out.println(loader);//这里打印出来是null,表示BootStrap
类加载器之间的父子关系和管辖范围:
BootStrap -------------------> JRE/lib/rt.jar
ExtClassLoader -----------------> JRE/lib/ext/*.jar
AppClassLoader ----------------> CLASSPATH指定的所有jar或目录
类加载器的委托机制:
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1)首先当前线程的类加载器去加载线程中的第一个类。
2)如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
3)还可以直接调用ClassLoader loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。
1)当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,而不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
2.自定义类加载器的编写原理分析
编写自己的类加载器:
自定义的类加载器必须继承ClassLoader。
不用重写loadClass()方法,因为loadClass()这个方法的作用就是先让上一级的类加载器去加载类,如果上一级的类加载器加载不了就调用findClass()方法来加载类。所以我们只需重写findClass()方法即可。
在findClass()方法中会调用到defineClass()方法,defineClass()方法的作用是得到.class文件的内容将其转换成字节码。
编程步骤:
1)编写一个对文件内容进行简单加密的程序。public class MyClassLoader { public static void main(String[] args) throws Exception { String srcPath = args[0]; //原文件 InputStream ips = new FileInputStream(srcPath); String destDir = args[1]; //存放加密后的文件的目录 String destFile = srcPath.substring(srcPath.lastIndexOf('\\')+1); //截取原文件的文件名 String destPath = destDir + "\\" +destFile; //组合文件目录和文件名 OutputStream ops = new FileOutputStream(destPath); cypher(ips,ops); //加密原文件 ips.close(); ops.close(); } //加密方法,将从ips输入流中得到的原文件加密后输出到ops输出流中。public class MyClassLoader extends ClassLoader //必须继承自ClassLoader { //重写父类的findClass方法,它的参数是需要加载的类名 protected Class<?> findClass(String name) throws ClassNotFoundException { String ClassFileName = classDir + "\\" +name + ".class"; //得到该class的完整地址,即加密过的文件 try { InputStream ips = new FileInputStream(ClassFileName); ByteArrayOutputStream bos = new ByteArrayOutputStream(); cypher(ips,bos); //解密 ips.close(); byte[] bytes = bos.toByteArray(); return defineClass(bytes,0,bytes.length); //返回由defineClass方法得到的该类的字节码 } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private String classDir; //要加载的.class文件的目录 public MyClassLoader(){} //无参的构造函数 public MyClassLoader(String classDir) //带参数(要加载的.class文件的目录)的构造函数 { this.classDir = classDir; } }
2)编写一个解密类加载器。 public class MyClassLoader extends ClassLoader //必须继承自ClassLoader { //重写父类的findClass方法,它的参数是需要加载的类名 protected Class<?> findClass(String name) throws ClassNotFoundException { String ClassFileName = classDir + "\\" +name + ".class"; //得到该class的完整地址,即加密过的文件 try { InputStream ips = new FileInputStream(ClassFileName); ByteArrayOutputStream bos = new ByteArrayOutputStream(); cypher(ips,bos); //解密 ips.close(); byte[] bytes = bos.toByteArray(); return defineClass(bytes,0,bytes.length); //返回由defineClass方法得到的该类的字节码 } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private String classDir; //要加载的.class文件的目录 public MyClassLoader(){} //无参的构造函数 public MyClassLoader(String classDir) //带参数(要加载的.class文件的目录)的构造函数 { this.classDir = classDir; } }
3)测试这个自己编写的解密类加载器Class<?> clazz = new MyClassLoader("itcastlib").loadClass("ClassLoaderAttachment");
//下面不能直接写ClassLoaderAttachment,因为该类尚未被正确加载,因此只能用它的父类Date来代替 Date d1 = (Date)clazz.newInstance(); System.out.println(d1);
4)类加载器的一个高级问题的实验分析在一个servlet中写这样几行代码,输出加载该servlet的ClassLoader及其父类,父类的父类,一直到最初的类加载器。
public class MyServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); ClassLoader loader = this.getClass().getClassLoader();//得到加载这个servlet的ClassLoader while(loader != null) //打印出该ClassLoader及其父类们 { out.println(loader.getClass().getName() + "<br>"); loader = loader.getParent(); } out.println(loader); out.close(); } }
这时在浏览器中运行得到的结果是:然后将这个servlet导出到我的C:\Program Files\Java\jdk1.6.0_10\jre\lib\ext文件夹中,直接运行,会在浏览器中输出错误信息:无法找到HttpServlet。这时需要将servlet-api.jar这个jar包也导入进C:\Program Files\Java\jdk1.6.0_10\jre\lib\ext这个文件夹中。再次运行,在浏览器中得到的结果是:org.apache.catalina.loader.WebappClassLoader org.apache.catalina.loader.StandardClassLoader sun.misc.Launcher$AppClassLoader sun.misc.Launcher$ExtClassLoader null
这个例子的结果充分验证了在本文的开头所提到的类加载器的委托机制:sun.misc.Launcher$ExtClassLoader null
每个类加载器加载类时,先委托给其上级类加载器。1)当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,而不是再去找发起者