JVM 自定义类加载器
编写原则
- 在JDK1.2之前,在自定义类加载器时,总会去重写loadClass方法,从而实现自定义的类加载类,但是JDK1.2之后已不再建议用户去覆盖loadClass方法,而是建议把自定义的类加载逻辑写在findClass方法中
- 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自已去编写findClass方法及获取字节码流的方式,使自定义类加载器编写更加简洁。
示例:
package com.classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class MyClassLoader extends ClassLoader { private static final String CLASS_PATH = System.getProperty("java.class.path"); // 编译生成的.class文件的bin目录 public MyClassLoader() { super(ClassLoader.getSystemClassLoader()); } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { System.out.println("findClass ...."); byte[] b = loadClassFile(className); return super.defineClass(className, b, 0, b.length); } private byte[] loadClassFile(String className) { System.out.println("loadClassFile ...."); className = className.replace(".", "/"); File file = new File(CLASS_PATH + "/" + className + ".class"); try { FileInputStream fis = new FileInputStream(file); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b = 0; while ((b = fis.read()) != -1) { baos.write(b); } fis.close(); return baos.toByteArray(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
说明:这个示例中MyClassLoader这个类没有重写loadClass方法,因此调用loadClass方法实际调用的是父类的loadClass方法,即loadClass 实质上是使用了系统类加载器加载的,而调用 findClass 方法才是真正意义上调用自定义类加载器加载
(1)系统类加载器加载同一个类型时(即类的全限名一致),加载的Class实例只有一个,并且只加载一次。双亲委派模型
MyClassLoader myClassLoader1 = new MyClassLoader(); Class<?> clazz1 = myClassLoader1.loadClass(Sample.class.getName()); MyClassLoader myClassLoader2 = new MyClassLoader(); Class<?> clazz2 = myClassLoader2.loadClass(Sample.class.getName()); System.out.println(myClassLoader1==myClassLoader2);// false System.out.println(clazz1==clazz2);// true
下面这个实例也可以证明:
同一个类的Class对象只有一个,当该类对象被加载后,就不会再去加载该类对应的Class对象,即使又执行了加载对象的操作;
对于每一个Class对象,可以通过其getClassLoader()方法获得其类加载器的引用,所以Class对象内部有指向其类加载器的引用;
Class<?> clazz1 = ClassLoader.getSystemClassLoader().loadClass(Sample.class.getName()); Class<?> clazz2 = ClassLoader.getSystemClassLoader().loadClass(Sample.class.getName()); System.out.println(clazz1.getClassLoader());// sun.misc.Launcher$AppClassLoader@2a139a55 System.out.println(clazz1==clazz2);// true
(2)用户自定义类加载器加载同一个类型时,可以加载多个Class实例。通过这种方式来破坏双亲委派模型,典型的应用如:Tomcat
MyClassLoader myClassLoader1 = new MyClassLoader(); Class<?> clazz1 = myClassLoader1.findClass(Sample.class.getName()); MyClassLoader myClassLoader2 = new MyClassLoader(); Class<?> clazz2 = myClassLoader2.findClass(Sample.class.getName()); System.out.println(myClassLoader1==myClassLoader2);// false System.out.println(clazz1==clazz2);// false
调用 findClass 表示这个类是通过自定义类加载器加载的
clazz1==clazz2 为false说明class实例在堆内存中属于不同的实例。
备注:因为上面 MyClassLoader中的findClass方法是protected类型,可能在其它包下无法访问!因此,需要再重写一个public类型的 loadClass 方法