Classloader中文叫做类加载器,作用就是把编译好的class文件加载到jvm中。但是,在jvm启动的时候,并不是一次性全部加载的。

三个原生的类加载器

  • Bootstrap ClassLoader最顶层的类加载器,主要加载核心类库,即jre lib下面的jar包。
  • Extention ClassLoader扩展类加载器,加载jre lib ext下的jar包。
  • AppClassLoader应用类加载器,加载当前应用即classpath下的所有类。

AppClassLoader的父加载器(不是父类)是Extention ClassLoaderExtention ClassLoader的父加载器(不是父类)是Bootstrap ClassLoader
我们可以写一些代码来验证一下。

public static void main(String[] args) {
        System.out.println(User.class.getClassLoader());
        System.out.println(RequestDTO.class.getClassLoader().getParent());
        System.out.println(RequestDTO.class.getClassLoader().getParent().getParent());
    }

其中User类是我们自己创建的一个类,运行结果如下:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@614ddd49
null

前面两个结果是预期之内,最后一个获取Extention ClassLoader的结果为null是为什么呢? 其实,Bootstrap ClassLoader是由C++编写的,它本身就是虚拟机的一部分,所以他并不是一个Java类,也就是无法在java代码中获取它的引用。

双亲委托

指的是类加载机制。
AppClassLoader加载class的时候,它首先会去缓存里面查这个类是不是已经加载过了,如果没有,则让自己的父加载器去加载,一直递归,直到Bootstrap,如果Bootstrap ClassLoader也判断这个类没有被加载过,则会去jre lib下面去加载。如果还没加载到,则让自己子类Extention ClassLoader去jre lib ext下加载,如果它也加载不到,则让它的子类AppClassLoader去classpath下加载。总结一句话就是,由下向上委托,由上向下查找
为什么是这样子可以从代码中体现。

ClassLoader类的loadclass方法

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //对应上面的先去缓存里面查找,底层是C的方法
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                    	//对应上面的让父加载器去查找,递归
                        c = parent.loadClass(name, false);
                    } else {
                    	//说明父加载器是Bootstrap ClassLoader,底层也是C的方法
                        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);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

自定义ClassLoader类

  • 所有自定义的ClassLoader类的父加载器默认都是AppClassLoader
  • 如果要自定义一个ClassLoader类,建议覆盖findClass()方法,而不要改写loadclass(),这样就可以保持双亲委派机制

实现

DiskClassLoader
package com.curtain.classloader;

import java.io.*;

/**
 * @Author Curtain
 * @Date 2022/11/1 16:11
 * @Description
 */
public class DiskClassLoader extends ClassLoader {

    private String mLibPath;

    public DiskClassLoader(String mLibPath) {
        this.mLibPath = mLibPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = getFileName(name);
        File file = new File(mLibPath, fileName);
        try{
        	//通过流将class文件读取进来,通过defineClass方法加载class类
            FileInputStream is = new FileInputStream(file);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            int len = 0;
            while ((len = is.read()) != -1){
                bos.write(len);
            }
            byte[] bytes = bos.toByteArray();
            bos.close();
            is.close();
            return defineClass(name, bytes, 0, bytes.length);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    private String getFileName(String name) {
        int index = name.lastIndexOf(".");
        if (index == -1) {
            return name + ".class";
        } else {
            return name.substring(index + 1) + ".class";
        }
    }
}

再写一个Test类,待会儿我们就用加载这个类。

package com.curtain.classloader;

/**
 * @Author Curtain
 * @Date 2022/11/1 14:42
 * @Description
 */
public class Test {
    public void say(){
        System.out.println("Say Hello");
    }
}

我们编译好Test类,将 class文件放在一个地方,比如,D盘下的lib文件夹。
最后就是测试类,DiskClassLoaderTest

package com.curtain.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author Curtain
 * @Date 2022/11/1 16:19
 * @Description
 */
public class DiskClassLoaderTest {
    public static void main(String[] args) {
        //创建自定义ClassLoader对象
        DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");
        try{
            //加载class文件
            Class c = diskClassLoader.loadClass("com.curtain.classloader.Test");
            if (c != null){
                try{
                    Object o = c.newInstance();
                    Method method = c.getDeclaredMethod("say", null);
                    method.invoke(o, null);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

运行结果就是成功执行了Test类里面的say()方法