Pre 双亲委派
何为打破双亲委派
举个例子 有个类 Artisan
我们希望通过自定义加载器 直接从某个路径下读取Artisan.class . 而不是说 通过自定义加载器 委托给 AppClassLoader ------> ExtClassLoader ----> BootClassLoader 这么走一遍,都没有的话,才让自定义加载器去加载 Artisan.class . 这么一来 还是 双亲委派。
我们期望的是 Artisan.class 及时在 AppClassLoader 中存在,也不要从AppClassLoader 去加载。
说白了,就是 直接让自定义加载器去直接加载Artisan.class 而不让它取委托父加载器去加载,不要去走双亲委派那一套。
我们知道 双亲委派的机制是在ClassLoader # loadClass方法中实现的,打破双亲委派,那我们是不是可以考虑从这个地方下手呢?
如何打破双亲委派
核心: 重写ClassLoader#loadClass方法
演示
刚才的思路是对的,要打破它,那就搞loadClass方法。
重写loadClass方法呗。
我们基于 JVM - 自定义类加载器 再来搞一搞
需要再此基础上 重写loadClass 方法
回归下双亲委派的源码
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); } else { //如果当前加载器父加载器为空则委托引导类加载器加载该类 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类 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; } }
那打破它,那我们就不要委托父加载器了呗,直接去findClass 不就好了?
我们把loadClass方法的源码copy过来 把双亲委派的部分代码去掉吧,走 改下
重写 ClassLoader#loadClass
public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } 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) { c = findClass(name); } if (resolve) { resolveClass(c); } return c; } } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); //defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } }
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 尝试加载,不存在直接去findClass ,不走委托父类 Class<?> c = findLoadedClass(name); if (c == null) { c = findClass(name); } if (resolve) { resolveClass(c); } return c; } }
运行下
失败原因探究
略微尴尬, Object.class 找不到 。 为啥 呢? 你加载Boss1的时候, Boss1的父类也需要被加载, 你又把双亲委派给关了, 这个自定义的加载器在本地路径下是找不到Object.class的 。
咋办? 放到自定义的加载器加载的路径下 ?
-----> 其实是不行的,Object 谁能篡改的了啊 ,Object只能由引导类加载器来加载。
临时解决办法
所以换个思路 ,自己的类路径下的对象走我自己的classLoader, 其他的类 还是走双亲委派
if ("com.gof.facadePattern.Boss1".equals(name)){ c = findClass(name); }else{ // 交由父加载器去加载 c = this.getParent().loadClass(name); }
验证是否成功
这个时候我们在AppClassLoader加载的路径下 再创建个Boss1 (如果走的还是双亲委派,那加载器肯定还是AppClassLoader)
看 是不是这个Boss1 还是被自定义的ClassLoader加载,如果是,说明打破成功。
应用下新建Boss1类
自定义加载路径D:/artisan/com/gof/facadePattern下保留Boss1.class
验证
输出结果
OK,双亲委派机制 打破成功。
这个在tomcat类加载机制中非常重要,所以需要彻底明白这一点。