Java高新技术5(类加载器(ClassLoader))
1.类加载器概述:
1.Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,
每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
2.类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,
显然必须有第一个类加载器不是不是java类,这正是BootStrap。
2.类加载器测试:
示意图:
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
package com.itheima.day5; import java.util.Map; /* 系统默认的三个主要类加载器: BootStrap ClassLoader:引导类加载器/启动类加载器 ExtClassLoader(Extension Class Loader):扩展类加载器 AppClassLoader:系统类加载器 */ public class ClassLoaderDemo1 { /** * @param args */ public static void main(String[] args)throws Exception { // TODO Auto-generated method stub //获取当前类的加载器 System.out .println(ClassLoaderDemo1.class.getClassLoader().getClass().getName()); System.out .println(System.class.getClassLoader()+"\n");//返回null,System类是由BootStrap(引导类加载器)加载 //该加载器采用C++编写,是在JVM开始运行的时候加载java的核心类 //其本身不属于Java任何类,因此返回null //三个类加载器查找的路径 System.out.println(System.getProperty("sun.boot.class.path")+"\n");//BootStrap加载该属性值指定的目录下.class(有包的话:package/.class或指定的.jar System.out.println(System.getProperty("java.ext.dirs")+"\n");//ExtClassLoader加载该属性指定的目录下的 所有.jar(该属性只代表了ext所在的目录) System.out.println(System.getProperty("java.class.path")+"\n");//AppClassLoader加载该属性指定的目录下.class(有包的话:package/.class)或指定的.jar//三个类加载器之间的关系 ClassLoader classLoader=ClassLoaderDemo1.class.getClassLoader(); while(classLoader!=null){ System.out.println(classLoader.getClass().getName()); classLoader=classLoader.getParent();//返回其委托的父类加载器 } System.out.println(classLoader+"\n"); /*for(Map.Entry<Object,Object> me : System.getProperties().entrySet()){ System.out.println(me.getKey()+"....."+me.getValue()); } */ } }
3.类加载器的委托机制:
类加载的委托机制:
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
1.首先当前线程的类加载器去加载线程中的第一个类。
2.如果类A中引用了类B(继承,实现,使用),Java虚拟机将使用加载类A的类装载器来加载类B。
3.还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。每个类加载器加载类时,又先委托给其上级类加载器。
当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,
不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?举例:
例如:发起者类加载器:AppClassLoader
在AppClassLoader加载该类前会委托给其父类加载器 ExtClassLoader,而ExtClassLoader还有父类加载器
在委托给BootStrap,BootStrap没有父类加载器->此时BootStrap开始在sun.boot.class.path指定的.jar中查找该类
->找到则加载该类
->没找到->让其直接子类加载器ExtClassLoader在java.ext.dirs的.jar中找->找到则加载该类
->没找到->再让其直接子类加载器AppClassLoader在java.class.path下中找->找到则加载该类
->没找到ClassNotFoundException委托机制优点:
类加载使用委托机制的优点:
1.便于集中管理
2.如果父类类加载已加载->直接拿过来用
如果有两个类加载器:
A和B,没有委托机制
如果两个自定义类分别指定了A和B加载,这两个类都要用到C.class
A加载C.class,B依然还要加载C.class->JVM中字节码不唯一
委托机制保证了Jvm中相同的字解码只有一份
4.自定义类加载器与类的简单解密/加密:
ClassLoader几个重要方法:
/* 自定义类加载器 继承ClassLoader,需要复写findClass方法 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException使用指定的二进制名称来加载类。此方法的默认实现将按以下顺序搜索类: 1.调用 findLoadedClass(String) 来检查是否已经加载类。 2.在父类加载器上调用 loadClass 方法。如果父类加载器为 null,则使用虚拟机的内置类加载器(委托机制) 3.调用 findClass(String) 方法查找类。 -->首先所有的父类加载器在自己相应的路径下查找 -->在调用findClass去找 不同的自定义加载器查找方式不同,但其委托的父类的加载器查找方式相同->因此继承ClassLoader复写findClass 以上是模板方法设计模式 */protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false);//在递归loadClass } else { c = findBootstrapClassOrNull(name);//再去委托BootStrap } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }package com.itheima.day5; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.File; public class CustomClassLoader extends ClassLoader{ /** * @param args; *///args[0]: bin\com\itheima\day5\EncryptClass.class
//args[1]:myEncryptClass public static void main(String[] args)throws Exception { // TODO Auto-generated method stub //args[0]作为源路径,args[1]作为目的路径 //我把加密和解密在同一个main中执行,在运行加密后注释掉 防止在删除bin下的EncryptClass后,由于找不到会报 //FileNotFoundException /* File file=new File(args[0]); FileInputStream fis=new FileInputStream(file); FileOutputStream fos=new FileOutputStream(args[1]+\\+file.getName()); encryptClass(fis,fos); fis.close(); fos.close();*/ //有名包下的类访问无名包下的类 //ClassLoader.getSystemClassLoader().loadClass("Test").newInstance(); Class cls=new CustomClassLoader("MyEncryptClass").loadClass("EncryptClass"); System.out.println(cls.newInstance().toString());//success } //简单加密 public static void encryptClass(InputStream in,OutputStream os)throws IOException{ int aByte=0; while((aByte=in.read())!=-1){ os.write(aByte^0xff);//最简单的加密->.class中的每个字节^255 } } private String classDir; public CustomClassLoader(String classDir) { super(); this.classDir = classDir; } @Override public Class<?> findClass(String name)throws ClassNotFoundException{ String path=classDir+"\\"+name+".class"; File file=new File(path); FileInputStream fis=null; byte[] byteArr=new byte[1024]; int aByte=0; int pointer=0; try{ fis=new FileInputStream(file.getAbsolutePath()); while((aByte=fis.read())!=-1) byteArr[pointer++]=(byte)(aByte ^ 255);//解密 } catch(Exception e){ e.printStackTrace(); } finally{ if(fis!=null) try{ fis.close(); } catch(Exception e){ e.printStackTrace(); } } return defineClass(null,byteArr,0,pointer); } }defineClass方法解析:
/* defineClass方法解析: EncryptClass.java是在com/itheima/day5包下创建的->那么在其.class中包含有包名信息->在defineClass(name,byteArr,0,pointer);->defineClass会以byteArr中的字节数据进行解析->如果我传的name和它解析的不一致->NoClassDefFoundError 这时可以把name换成com.itheima.day5.EncryptClass(全限定名)测试一下,OK.而传入null也可以成功意思就是说:"哥们我不知道这个完整类名,你根据.class文件中的字节解析一下吧,这时候不在比对你指定的name" */
5.ClassNotFoundException与NoClassDefFoundError
摘自:http://www.ibm.com/developerworks/cn/java/j-dclp2.html
1.
ClassNotFoundException
是最常见的类装入异常类型。它发生在装入阶段。Java 规范对ClassNotFoundException
的描述是这样的:当应用程序试图通过类的字符串名称,使用以下三种方法装入类,但却找不到指定名称的类定义时抛出该异常。
- 类
Class
中的forName()
方法。- 类
ClassLoader
中的findSystemClass()
方法。- 类
ClassLoader
中的loadClass()
方法。所以,如果显式地装入类的尝试失败,那么就抛出
ClassNotFoundException
。清单 1 中的测试用例提供的示例代码抛出了一个ClassNotFoundException
发起者类加载器及其所有父类加载器均找不到->
ClassNotFoundException
2.
当类
A
扩展(使用)了类B,
当类A
装入时,类装入器会隐式地装入类B
。如果类B
不存在,所以抛出NoClassDefFoundError
。另外就是我上面的defineClass(String,byte[],int,int):其解析的类名与name不一致->
NoClassDefFoundError