new操作时调用当前线程的类加载器,还是调用方的类加载器 (三)
当进行new的时候,使用的是
1 当前代码所在类加载器
2 调用当前代码的代码所在类加载器
本文设计一个案例予以实践:
调用方在AppClassLoader系统类加载器(Reflection.getCallerClass().getClassLoader())
new 代码所在类加载器为MyUrlClassLoader(XXX.class.getClassLoader())
查看ByW static代码块打印的是父亲还是儿子
为了避免双亲委派干扰,毕竟即使是由MyUrlCLassLoader加载,它在查看缓存没有ByW后,会问AppClassLoader要,而那里面确实也有一个
为此,我们应用类加载器顺序-另一种绕开双亲委派的方式的结论,先让MyUrlClassLoader findClass(ByW)一下,缓存起来,然后来看编译器对于new这个问题上,是先用哪个类加载器加载(ByW为调用链底层独立类,可使用findClass)
代码结构:
MyMain
lc4
Main MyUrlClassLoader(MySub)
ByW
MySub
lc4
W {new ByW}
ByW
MyMain:
public class ByW { public void print() { System.out.println("ByW父亲"); } }
package lc4; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; /** * https://www.cnblogs.com/silyvin/articles/12390277.html * Created by joyce on 2020/3/1. */ public class Main { 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); /** * 先强行缓存进子加载器方法区,避免双亲委派影响我们的试验结果和判断 * 若没有这句,仍然打印父亲 */ myUrlClassLoader.findClass("lc4.ByW"); /** * 强行让父加载器也缓存进方法区,添加干扰和公平 * 至此,系统类父亲与儿子方法区都有lc4.ByW了,看最后调用哪个? */ new ByW(); // 由自定义类加载器加载W,调用print,中new W()时再加载ByW // 是由W的类加载器加载? // 还是调用print的代码所在的类加载器,也就是以下这些代码所在类加载器-系统类加载器? // 结果显示由W的类加载器加载,this.getClass.getClassLoader // 证明加载器缓存(方法区)父子具有隔离型,及不具有双亲委派性,子加载器加载过的类,不需要惊动父亲,即使父亲缓存里也有一份 Class c1 = myUrlClassLoader.loadClass("lc4.W"); doInvoke(c1); } 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); } } }
MySub:
public class W { //@CallerSensitive public void print() { new ByW().print(); /** // 调用方代码所在类加载器 System.out.println(Reflection.getCallerClass().getClassLoader()); // 本代码所在类加载器 System.out.println(W.class.getClassLoader()); // 最终结果打印 System.out.println(ByW.class.getClassLoader()); **/ } }
public class ByW { public void print() { System.out.println("ByW儿子"); } }
结果:
无myUrlClassLoader.findClass("lc4.ByW");
打印:ByW父亲
调试结果:
可以看到,是先用MyUrlClassLoader去加载的,只不过因为没有findClass过,没有缓存,所以后来委托parent加载
有myUrlClassLoader.findClass("lc4.ByW");
打印:ByW儿子
调试结果:
以lc4.ByW为name的loadclass都没进去过,废话,都new过了,方法区肯定有了,不用再次loadClass,这与显式loadClass或forName不同,不需要再显式调用native findLoadedClass
结论:
new 方式使用的是当前代码所在类加载器加载,而不是调用当前代码的代码所在类加载器加载