一、自定义类加载器的一般步骤
Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制。一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到*类加载器。如果父类加载器加载不了,依次再使用其子类进行加载。当然这类所说的父类加载器,不一定他们之间是继承的关系,有可能仅仅是包装的关系。
Java之所以出现这条机制,因为是处于安全性考虑。害怕用户自己定义class文件然后自己写一个类加载器来加载原本应该是JVM自己加载的类。这样会是JVM虚拟机混乱或者说会影响到用户的安全。下面我们来自己实现一个类加载器,其中主要就是继承ClassLoader类。我们有必要明白:
虽然在绝大多数情况下系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来。下面将通过两个具体的实例来说明类加载器的开发。
①ClassLoader加载类的顺序
1调用findLoadedClass(String) 来检查是否已经加载类
2在父类加载器上调用loadClass方法。如果父亲不能加载,一次一级一级传给子类
3调用子类findClass(String) 方法查找类。若还加载不了就返回ClassNotFoundException,不交给发起请求的加载器的子加载器
②实现自己的类加载器
1 获取类的class文件的字节数组,如loadClassData方法
2 将字节数组转换为Class类的实例,重写findClass中调用的defineClass方法
package cn.M_ClassLoader2; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; public class ClassLoaderTest { public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // 新建一个类加载器 MyClassLoader cl = new MyClassLoader("myClassLoader"); // 加载类,得到Class对象 Class<?> clazz = cl.loadClass("cn.M_ClassLoader2.Animal"); // 得到类的实例 Animal animal = (Animal) clazz.newInstance(); animal.say(); } } class Animal { public void say() { System.out.println("hello world!"); } } class MyClassLoader extends ClassLoader { // 类加载器的名称 private String name; // 类存放的路径 private String path = MyClassLoader.getSystemClassLoader().getResource("").getPath();; MyClassLoader(String name) { this.name = name; } MyClassLoader(ClassLoader parent, String name) { super(parent); this.name = name; } /** * 重写findClass方法 */ @Override public Class<?> findClass(String name) { byte[] data = loadClassData(name); return this.defineClass(name, data, 0, data.length); } public byte[] loadClassData(String name) { try { name = name.replace(".", "//"); FileInputStream is = new FileInputStream(new File(path + name + ".myclass")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); int b = 0; while ((b = is.read()) != -1) { baos.write(b); } System.out.println("我是自定义类加载器哦!"); return baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } return null; } }
一般来说自己开发的类加载器只需要覆写findClass(String name)
方法即可。java.lang.ClassLoader
类的方法loadClass()
封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()
方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()
方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()
方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写loadClass()
方法,而是覆写findClass()
方法。
二、自定义类加载器的运行问题
由于只重写了findClass方法并没有重写loadClass方法,故没有改变父类委托机制。也就数说如果某个.class可以被父类加载,我们自定义的类加载器就不会被执行了。比如Animal.java被自动编译为Animal.class放在bin目录下,AppClassLoader完全可以加载,所以就不调用自定义的加载器了。
尝试办法1:把Animal.class放在别的目录中比如D盘的根目录下
报 Class A can not access a member of class B with modifiers ""错。Java语言中的包访问成员实际上指的是运行时包访问可见,而不是编译时。因此当你试图访问不在同一个runtime package的成员时,即便在编译时它们在同一个包内,但是却由不同的class loader加载,也同样会得到java.lang.IllegalAccessException: Class A can not access a member of class B with modifiers "" 这样的异常。
尝试办法2:把该Animal.class的后缀名为.myClass,让AppClassLoader找不到
网上有人说可以解决,但是我实验的结果是会和办法1报一样的异常。
尝试办法3:解决方案是通过扩展自定义的ClassLoader,重写loadClass方法先从当前类加载器加载再从父类加载器加载。
该解决办法是可以解决的,网址是http://blog.csdn.net/zhangxinrun/article/details/6161426
参考博客
http://blog.csdn.net/zhangxinrun/article/details/6161426
http://blog.csdn.net/zhouysh/article/details/762300
http://blog.csdn.net/a352193394/article/details/7343385
http://blog.csdn.net/huangbiao86/article/details/6910152
http://www.cnblogs.com/feiling/archive/2012/08/29/2662909.html (加密字节码)