类加载器顺序-另一种绕开双亲委派的方式【yetdone 危险的案例】and 加载顺序总结 and 三种绕开方式总结
加载顺序总结
MyClassLoader查看本加载器缓存,native findLoadedClass
MyClassLoader交给父加载器loadclass
MyClassLoader调用本类的findClass
MyClassLoader调用本类的defineClass
JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】认为:
第一次loadclass MyJdbcProxy
MyClassLoader查看缓存(Class<?> c = findLoadedClass(name);)有无名为MyJdbcProxy的类——没有
MyClassLoader交给父加载器,因为MyJdbcProxy父加载器也没有,因为我们改名了lc3.jars.JdbcProxy---->MyJdbcProxy——也没有
MyClassLoader调用findClass,然后defineClass,native方法将类缓存入MyClassLoader
MyClassLoader第二次loadClass MyJdbcProxy,再次跑到了defineClass,导致异常
作者猜测 findClass/defineClass与一开始的findLoadedClass相对应,findClass之后,在本加载器存储了一个缓冲,下一次loadClass之后,在交给父亲之前,会先在自己本地查找,findClass/defineClass作为本地是否有类的缓存的依据
所以在父加载器与子加载器同时有类H时,先子加载器findClass一下,那么此后所有loadClass都会加载自己的
这就像小孩子考试成绩要爸爸签字,先让爸爸试着签一个,然后藏起来,以后考试都可以先拿这个藏起来的签字
为了验证这个想法,做了如下设计:
MyMain
lc4
FuckPreFindClass MyUrlClassLoader(MySub.url)
H {new ByW}
ByW
MySub
lc4
H {new ByW}
ByW
FuckPreFindClass中的代码:
package lc4; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; /** * Created by joyce on 2020/3/5. */ public class FuckPreFindClass { public static void main(String []f) throws Exception { String dir = "file:/Users/sunyuming/Documents/tool/jars//MySub-1.0.0-jar-with-dependencies.jar"; URL url = null; try { url = new URL(dir); } catch (MalformedURLException e) { e.printStackTrace(); } URL[] urls = {url}; MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls); /** * 重要的一句,H依赖的ByW,必须在这里进行findClass */ myUrlClassLoader.findClass("lc4.ByW"); // 可在ClassLoader 761行preDefineClass断点 System.out.println(11); Class c1 = myUrlClassLoader.loadClass("lc4.H"); // AppClassLoader define lc4.H doInvoke(c1); // AppClassLoader define lc4.ByW System.out.println(21); // 用findClass取得H;并强行将H缓存进子加载器方法区 Class c2 = myUrlClassLoader.findClass("lc4.H"); // MyUrlCLassLoader define lc4.H doInvoke(c2); // nothing System.out.println(31); Class c3 = myUrlClassLoader.loadClass("lc4.H"); doInvoke(c3); /** * 我本意是在这里加这一句,理想中,以上3个根据双亲委派都应该打印ByW父亲 * 然而报了重复define异常 * 所以使用findClass预注册是危险的 */ // myUrlClassLoader.findClass("lc4.ByW"); System.out.println(41); Class c4 = myUrlClassLoader.loadClass("lc4.H"); doInvoke(c4); } private static void doInvoke(Class c) throws Exception { Object o = c.newInstance(); Method method = c.getMethod("print"); method.invoke(o); } private static class MyUrlClassLoader extends URLClassLoader { public MyUrlClassLoader(URL[] urls) { super(urls); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { return super.findClass(name); } } }
*依赖的类谁来调?哦,是 c1.getClassLoader().loadClass() —— new操作时调用当前线程的类加载器,还是调用方的类加载器 (三)【重点】
输出:
11
H父亲
ByW父亲
21 —— 从这里开始后的findClass及loadClass均返回自定义加载器的类C,然后调用C.class.getClassLoader().loadClass("lc4.ByW")
H儿子
ByW儿子 ——由于自定义加载器一开始就findClass("lc4.ByW")了一下缓存,故都没交给parent就直接返回了自己的类
31
H儿子
ByW儿子
41
H儿子
ByW儿子
可以看到,第三次loadClass时,没有去调用父加载器有的那个H,而是先从缓存中拿到第二次findClass的H,这是一个重要的结论
补充:
1 不一定通过native findLoadedClass发现缓存的类,可能是某个我还没发现的方法直接从方法区拿,new操作时调用当前线程的类加载器,还是调用方的类加载器 (三) 中通过调试,new的方式,都没进loadClass,所以情况可能是:
2 加载顺序总结
未知的某个函数查看缓存或方法区,应对new,对于其它loadclass或forname,至下一节点
MyClassLoader查看本加载器缓存,native findLoadedClass
MyClassLoader交给父加载器loadclass
MyClassLoader调用本类的findClass
MyClassLoader调用本类的defineClass
3 本例的实践认为,改写并直接调用urlclassloader.findClass是危险不可控的行为,特别是在本例这种有多级依赖的情况,这也与实际情况相近
除非findClass调用链底层的独立类
4 三种绕开方式为:
1)使不可见——tomcat、jetty把某个项目的依赖jar没有放在影响范围较大的系统类加载器,而使用一个单独的WebCLassLoader加载,类加载器隔离朴实案例,类的相同通过对是否为同一个类加载器进行判断
缺点:有时候很难使之不可见
2)把jar包作为资源文件读取,改写loadClass
缺点,较难控制,JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】,使用resource中的jar包资源作为UrlClassloader(二)算是一次尝试,但对于改写findClass(而且这两篇文章只是侧重于读取资源jar包,findClass无法绕开双亲委派), mysql某个版本就不买帐
类加载器顺序-另一种绕开双亲委派的方式(二)通篇改写loadClass 使用改写loadClass对本法做了一个简单实践; 然而,在更复杂的实践 类加载器隔离朴实案例【重点】【yetdone】 中,证明此法仍然是极不稳定,风险极大的
3)每次调用loadClass、调用一次findClass预加载,担保以后使用loadClass不会跑到父亲那边找(本文)
——缺点:范围小,内部依赖类不可能各个代码显式findClass,new的类仍然会走loadClass到parent走一圈,本文仅能作为小实验的小技巧
而且很危险而不可控,建议使用第1)2)种方式;若为调用链底层的独立类,可用(new操作时调用当前线程的类加载器,还是调用方的类加载器 (二) ,new操作时调用当前线程的类加载器,还是调用方的类加载器 (三)这两篇文章就没办法都用了findClass去一个底层独立类)