十五、自定义类加载器在复杂类加载情况下的运行分析

准备动作:

public class MyCat {
    public MyCat() {
        System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
    }
}

public class MySample {
    public MySample() {
        System.out.println("MySample is loader by:" + this.getClass().getClassLoader());
        new MyCat();
    }
}
package com.jvm.classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyTest17 extends ClassLoader {

    private String classLoaderName;

    private String path; // class文件路径

    private final String fileExtension = ".class";

    public MyTest17(String classLoaderName) {
        super(); // 将系统类加载器作为该类加载器的父类加载器
        this.classLoaderName = classLoaderName;
    }

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

    public MyTest17(ClassLoader parent, String classLoaderName) {
        super(parent); // 显式指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }


    @Override
    public String toString() {
        return "MyTest17{" +
                "classLoaderName='" + classLoaderName + '\'' +
                '}';
    }


    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass:" + className);
        System.out.println("class loader name:" + this.classLoaderName);
        // 父类中findClass方法返回的是一个异常,所以必须要重写
        byte[] data = this.loadClassData(className);
        return this.defineClass(className, data, 0, data.length);
    }

    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        className = className.replace(".", "/");
        try {
            is = new FileInputStream(new File(this.path + className + this.fileExtension));
            System.out.println(this.path + className + this.fileExtension);
            baos = new ByteArrayOutputStream();
            int ch;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return data;
    }
public class MyTest18 {
    public static void main(String[] args) throws Exception {
        MyTest17 loader1 = new MyTest17("loader1");
        Class<?> aClass = loader1.loadClass("com.jvm.classloader.MySample");
        System.out.println("aclass: " + aClass.hashCode());
        Object o = aClass.newInstance();
    }
}

我的想法:
MySample应该是应用类加载器加载的,但是MySample里面的MyCat是哪个类加载器加载的呢?应该也是app吧。
实际结果:

aclass: 1163157884
MySample is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2

分析,通过反射的方式是对MySample的主动使用,会初始化,当然也会被加载,而在MySample的初始化方法中,new了一个MyCat对象,当然也会被初始化和加载。
如果注视掉反射那行Object o = aClass.newInstance();,则MyCat不会被初始化,就不一定会被加载,可以通过jvm参数来观察是否被加载。

此时的核心问题是:加载MyCat的类加载器跟加载MySample的是不是同一个类加载器?

操作:在桌面上保存这个这两个类的class文件,main方法如下:

public static void main(String[] args) throws Exception {
        MyTest17 loader1 = new MyTest17("loader1");
        loader1.setPath("C:/Users/lifeline张/Desktop/");
        Class<?> aClass = loader1.loadClass("com.jvm.classloader.MySample");
        System.out.println("aclass: " + aClass.hashCode());
        Object o = aClass.newInstance();
    }

1、删除ide中的mycat的class文件,运行代码。此时会报class文件找不到异常。两者都是由app加载的。运行结果如下:

aclass: 1163157884
MySample is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: com/jvm/classloader/MyCat
	at com.jvm.classloader.MySample.<init>(MySample.java:6)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at com.jvm.classloader.MyTest18.main(MyTest18.java:9)
Caused by: java.lang.ClassNotFoundException: com.jvm.classloader.MyCat
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 7 more

2、如果删除MySample的class文件,保留mycat的,此时正常输出,sample由loader1加载,cat由app加载。

findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2

3、在2的基础上,改变MyCat的实例化方法:

public class MyCat {
    public MyCat() {
        System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
        System.out.println(MySample.class);
    }
}

输出结果为:

findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
Exception in thread "main" java.lang.NoClassDefFoundError: com/jvm/classloader/MySample
	at com.jvm.classloader.MyCat.<init>(MyCat.java:6)
	at com.jvm.classloader.MySample.<init>(MySample.java:6)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at com.jvm.classloader.MyTest18.main(MyTest18.java:9)
Caused by: java.lang.ClassNotFoundException: com.jvm.classloader.MySample
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 8 more

加载是都加载完了,但是在mycat中没有找到mysqmple的Class对象。本质原因是两个类是由不同的类加载器加载,属于不同的命名空间

4、改造mycat与myxample如下:

public class MyCat {
    public MyCat() {
        System.out.println("MyCat is loader by:" + this.getClass().getClassLoader());
//        System.out.println(MySample.class);
    }
}
public class MySample {
    public MySample() {
        System.out.println("MySample is loader by:" + this.getClass().getClassLoader());
        new MyCat();
        System.out.println("from MySample" + MyCat.class);
    }
}

将两者class文件放到桌面上,删除sample的class文件,运行结果如下:

findClass:com.jvm.classloader.MySample
class loader name:loader1
C:/Users/lifeline张/Desktop/com/jvm/classloader/MySample.class
aclass: 356573597
MySample is loader by:MyTest17{classLoaderName='loader1'}
MyCat is loader by:sun.misc.Launcher$AppClassLoader@18b4aac2
from MySampleclass com.jvm.classloader.MyCat

这个跟3形成了对比,自定义类加载器命名空间里面包含了其父类的命名空间中的类。

总结:**子加载器所加载的类可以访问父加载器加载的类;而父加载器加载的类无法访问子加载器所加载的类。**核心原因就是:命名空间由该加载器及所有父加载器所加载的类组成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值