$1.4、复杂类加载器(加载过程中其他的对象引用)详解

介绍完基本的类加载器后,我们再扩展一点,如果在加载过程中,还使用到了其他对象的引用,那我们加载是怎样进行的呢?

上代码(用我们上篇文字所自定义的加载器,然后去加载MySample和MyCat类,因MySample里面主动创建了MyCat对象,因此对MySampl的加载过程中,也产生了对MyCat的对象引用),最后我们看输出结果

public class MySample {

  /*  static {
        System.out.println("sample static block invoke");
    }*/

    public MySample() {
        //后续测试中,new对象时即为对类的首次主动使用,因此需要初始化,需要进行加载动作
        System.out.println("sample class load:" + this.getClass().getClassLoader());
        new MyCat();
    }
public class MyCat {

    public MyCat() {
        System.out.println("myCat load:" + this.getClass().getClassLoader());
    }

使用自定义的加载器进行加载

public class MyTest2 {
    public static void main(String[] args) throws Exception {
        //首先我们用自定义的加载器去加载sample
        MyTest1 load1 = new MyTest1(null, "load1");
        Class<?> aClass = load1.loadClass("jp.zhang.classloader.MySample");

        //
        Object o = aClass.newInstance();
    }

通过MySample的字节码文件进行反射创建对象时,调用了空参构造方法,所以又调用了MyCat的构造方法,得出的输出如下:

由此得出的结论:加载类的过程时,如果还产生了其他类的对象引用,那么也会使用该类的加载器去加载被引用的类,在加载的过程中,同样也是遵循双亲委托机制。

为了证明还是遵循双亲委托,我们再做个实验:我们指定自定义器的加载路径(目的是为了区分不同的场景,在整个加载过程中,能看出使用不同的加载器)在classPath路径中,先保留MySample.class文件,将MyCat.class移到我们指定的路径中,最新的自定义类加载器代码:

package jp.zhang.classloader;

import java.io.*;

/**
 * created by: marshal
 * createDate: 2019/12/16 10:19 PM
 */
public class MyTest1 extends ClassLoader {

    public String classLoaderName;//为了后面获取classloader名称时使用自定义名称

    public ClassLoader parent;//指定使用自己的父类加载器,同时因为双亲模式,如果需要真正使用到自定义的加载器加载,需要将父类加载器置为null,或者将class文件放入到别的路径中。

    //自定义父类加载器
    public MyTest1(ClassLoader parent, String classLoaderName) {
        super(parent);
        this.parent = parent;
        this.classLoaderName = classLoaderName;
    }

    //自定义加载器
    public MyTest1(String classLoaderName) {
        super();
        this.classLoaderName = classLoaderName;
    }

    public String path;

    public void setPath(String path) {
        this.path = path;
    }

    public static final String CLASS_PREFIX = ".class";

    /**
     * classLoader,根据文档说明,需要复写其findClass方法
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //1、先要将类的字节名称转换成字节码数组
//        System.out.println("find class method invoke");
        byte[] data = this.loadClassData(name);
        //2需要将获取到的字节数组转换成class对象,后续如果需获取到该class文件代表类的对象
        return defineClass(name, data, 0, data.length);
    }

    /**
     * 通过类的路径,将名称对应的类文件读取里面的内容,转换成byte数组
     *
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        System.out.println("start transform file to byte array for class");
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            //将二进制名称转换成字节码路径
            String replace = name.replace(".", "/");
            //如果path设定,就使用path路径的代码,否则默认使用classpath的target输出目录

            String target = null == path ? "target/classes/" + replace : path + replace;
            File file = new File(target + CLASS_PREFIX);
            in = new FileInputStream(file);
            out = new ByteArrayOutputStream();

            int read;
            while (-1 != (read = in.read())) {
                out.write(read);
            }
            return out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("byte array is null");
        return null;
    }


    public static void main(String[] args) throws Exception {
        MyTest1 load1 = new MyTest1(null, "load1");
        Class<?> aClass = load1.loadClass("jp.zhang.classloader.MyTest2");
        System.out.println(aClass.getClassLoader());

    }
}

测试:把MySample保留,将MyCat移除到指定目录,得到以下结果:

随后我们进行测试对比,将MyCat保留在classpath中,MySample移到指定目录是不是很神奇?原因也是跟双亲委托模式有关:

在第一种情况下,MySample是由app应用类加载器完成的,后续再加载MyCat时,按照上面的结论,也是用应用加载器进行加载,整个双亲下来都加载不了,本身也找不到该class文件,这也说明加载器加载对象引用的类时,也是使用相同的类加载器,而且是作为第一层的加载器,然后遵循双亲委派。

而第二种场景,父类加载不了MySample,于是由子加载器进行加载,后续到MyCat时,也是由子加载器尝试加载,因为双亲委托,刚好父类应用类加载器可以加载,所以看到对应的加载器为应用类加载器,这也是子类加载器可以看到父类加载器所加载器类的体现。

扫描二维码关注公众号,回复: 9312148 查看本文章

以上就是加载过程中 ,对引用对象的加载所遵循的规则,将该加载器同样加载引用,然后遵循双亲委派模式。

下一节,我们介绍加载器的命名空间

发布了12 篇原创文章 · 获赞 1 · 访问量 376

猜你喜欢

转载自blog.csdn.net/weixin_39800596/article/details/103571778