Java中ClassLoader的双亲委托

    大家可能都听说过Java的类加载机制,是双亲委托模式,听起来有点神秘,但是为什么会叫这个名称呢?

    今天我们就来简单的介绍下!

    所谓双亲委托,
       1. 就是首先当前ClassLoader判断该Class是否已经加载;
       2. 如果没有,就委托给父加载器进行查找(自己先不查找),这样依次的进行递归,直到委托到最顶层的ClassLoader;
       3. 如果最顶层ClassLoader找到了这个Class,就直接返回,如果没有,就继续依次向下查找;
       4. 如果还没找到,就会交由 当前ClassLoader 去查找。

    话不多说,实践才是真理,我们先来做个Demo,验证下这条规则。

    我们首选来自定义一个ClassLoader,如下:

/**自定义--类加载器*/
public class SelfClassLoader extends ClassLoader {
    private String path;    //表示加载class的路径

    public SelfClassLoader(String path) {
        this.path = path;
    }

    @Override
    //查找并加载class
    protected Class<?> findClass(String name) {
        MyUtils.println("SelfClassLoader --- findClass -- name: " + name);
        Class tempClass = null;
        byte[] classData = loadClassData(name);
        if (classData != null) {
            MyUtils.println("SelfClassLoader --- defineClass");
            tempClass = defineClass(name, classData, 0, classData.length);
        }
        return tempClass;
    }

    //将class文件,加载为byte数据
    private byte[] loadClassData(String name)
    ...

  很简单的一个类,一个属性path,表示当前需要加载的数据。

  覆盖findClass方法,这个方法就是用来加载并查找class的方法,我们将path下的class文件,加载成byte数据,然后调用父类的definClass方法,就可以转换成需要的Class对象。

   下面我们来定义一个最简单的类 TestOne,只有一个方法out,如下:

public class TestOne {
    public void out() {
        System.out.println(" 调用了TestOne.out方法!");
    }

}

  把它生成的class文件(IDE自动生成),放到磁盘下某个目录(对应SelfClassLoader 的 path),然后来加载并调用试试:

public static void main(String[] args) {
        test1();
    }
    public static void test1() {
        SelfClassLoader selfClassLoader = new SelfClassLoader("e:\\class\\");
        try {
            Class clazz = selfClassLoader.loadClass("com.testjava.TestOne");
            if (clazz != null) {
                Object testTwo = clazz.newInstance();
                ClassLoader loader = clazz.getClassLoader();
                int i = 0;
                while (loader != null) {
                    //循环打印 类加载器的父加载器
                    MyUtils.println(++i + ": 类加载器: " + loader);
                    loader = loader.getParent();
                }
                Method out = clazz.getDeclaredMethod("out");
                out.invoke(testTwo);
            }
...

  我们来看看打印的日志:

SelfClassLoader --- findClass -- name: com.testjava.TestOne
SelfClassLoader --- defineClass

1 类加载器: com.testjava.SelfClassLoader@4b67cf4d
2 类加载器: sun.misc.Launcher$AppClassLoader@14ae5a5
3 类加载器: sun.misc.Launcher$ExtClassLoader@12a3a380

 调用了TestOne.out方法!

   确实如预计效果,调用到了TestOne.out方法,而且我们还打印了TestOne的类加载器结构,它的父加载器有AppClassLoader,而AppClassLoader的父亲是ExtClassLoader,其实还有一个启动加载器,不过因为它是C编写的,这里无法打印。

   (注意:AppClassLoader ExtClassLoader是系统自带的类加载器,我们不用管)

   现在我们把TestOne的源文件直接放到当前目录下,看我们定义的加载器,还会不会加载它?

   打印日志如下:

1: 类加载器: sun.misc.Launcher$AppClassLoader@14ae5a5
2: 类加载器: sun.misc.Launcher$ExtClassLoader@4b67cf4d

 调用了TestOne.out方法!

  为什么会这样,因为TestOne的加载已经由AppClassLoader完成,所以不需要我们自定义的ClassLoader再去加载。

 

  我们去看下ClassLoader的源码,找找原因:

  这就是ClassLoader最核心的方法 loadClass

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ 
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);    //查找当前加载器的缓存,是否有此类
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {         //父加载器不为空,则交由它去加载
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);  //所有的父加载器都没有加载,则由最底层的儿子来加载
     ....
      return c
}

  从中我们找到了原因,确实和开头所说的一样,先看自己有没有,否则委托给父加载器进行查找(自己先不查找),直至最后,由自己来加载。

©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页