类加载器
用来加载Java类到Java虚拟机中。一般来说,Java虚拟机使用Java类的方式如下:Java 源程序(.java 文件)在经过Java编译器编译之后就被转换成字节码(.class 文件)。类加载器就是负责读取Java字节代码,并转换成 java.lang.Class
类的一个实例。
Java应用环境中不同的class分别由不同的ClassLoader负责加载。
一个jvm中默认的类加载器有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader:
- 引导类加载器Bootstrap ClassLoader 负责加载Java基础类,主要是 %JRE_HOME/lib/ 目录下的rt.jar、resources.jar、charsets.jar和class等。
- 扩展类加载器Extension ClassLoader 负责加载Java扩展类,主要是 %JRE_HOME/lib/ext 目录下的jar和class。
- 应用类加载器App ClassLoader 负责加载当前Java应用的classpath中的所有类。
类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。
而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:
自定义类加载器
由于一些特殊的需求,我们可能需要定制ClassLoader的加载行为,这时候就需要自定义ClassLoader了。自定义ClassLoader需要继承ClassLoader抽象类,重写findClass方法。这个方法定义了ClassLoader查找class的方式。因为类加载器是基于委托机制,它会自动向父类加载器委托,如果父类没有找到,就会再去调用我们重写的findClass方法加载。
1.定义一个需要被加载的类
1 import java.util.Date; 2 public class ClassTest extends Date{ 3 public Sting toSring() { 4 return"hello,java!"; 5 } 6 }
2.定义一个用来加密字节码的类
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 7 public class ClassEncrypt extends MyClassLoader{ 8 public static void main(String[] args) throws IOException { 9 //要加密的字节码文件路径 10 String srcPath="F:\\Workspaces\\MyEclipse 10\\classload\\bin\\test_classload\\ClassTest.class"; 11 //加密之后输出的字节码文件路径 12 String destPath="F:/ClassTest.class"; 13 FileInputStream fis=new FileInputStream(srcPath); 14 FileOutputStream ofs=new FileOutputStream(destPath); 15 cypher(fis, ofs); 16 fis.close(); 17 ofs.close(); 18 } 19 //按字节异或 20 private static void cypher(InputStream in,OutputStream out) throws IOException{ 21 int b=-1; 22 while((b=in.read())!=-1){ 23 out.write(b^0xff); 24 } 25 } 26 }
3.自定义类加载器
1 import java.io.ByteArrayOutputStream; 2 import java.io.FileInputStream; 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 9 public class MyClassLoader extends ClassLoader{ 10 11 @SuppressWarnings("deprecation") 12 @Override 13 protected Class findClass(String name) throws ClassNotFoundException { 14 try { 15 //需要加载的.class字节码的位置 16 String classPath="D:/ClassTest.class"; 17 18 FileInputStream fis=new FileInputStream(classPath); 19 ByteArrayOutputStream bos=new ByteArrayOutputStream(); 20 cypher(fis, bos); 21 fis.close(); 22 byte[] bytes=bos.toByteArray(); 23 return defineClass(bytes, 0, bytes.length); 24 } catch (FileNotFoundException e) { 25 e.printStackTrace(); 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 return super.findClass(name); 30 } 31 32 private static void cypher(InputStream in,OutputStream out) throws IOException{ 33 int b=-1; 34 while((b=in.read())!=-1){ 35 out.write(b^0xff); 36 } 37 } 38 }
4.测试类
1 import java.lang.reflect.Method; 2 import java.util.Date; 3 4 public class Test { 5 public static void main(String[] args) throws Exception { 6 Class clazz=new MyClassLoader().loadClass("test_classload.ClassTest"); 7 //父类引用 8 Date d1 = (Date)clazz.newInstance(); 9 System.out.println(d1); 10 //反射调用 11 Method m1 = Class.forName("test_classload.ClassTest").getMethod("toString", null); 12 System.out.println(m1.invoke(Class.forName("test_classload.ClassTest").newInstance(), null)); 13 14 } 15 }