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 方式使用的是当前代码所在类加载器加载,而不是调用当前代码的代码所在类加载器加载

posted on 2020-03-01 16:08  silyvin  阅读(519)  评论(0编辑  收藏  举报