android_classloader 双亲委托模式

579 阅读5分钟
原文链接: click.aliyun.com

概述

ClassLoader的双亲委托模式:classloader 按级别分为三个级别:最上级 : bootstrap classLoader(根类加载器) ; 中间级:extension classLoader (扩展类加载器) 最低级 app classLoader(应用类加载器)。

根(Bootstrap)类加载器:该加载器没有父加载器。它负责加载虚拟机的核心类库,如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统,属于虚拟机的实现的一部分,它并没有继承java.lang.ClassLoader类。

扩展(Extension)类加载器:它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库,如果把用户创建的JAR文件放在这个目录下,也会自动由扩展类加载器加载。扩展类加载器是纯Java类,是java.lang.ClassLoader类的子类。

系统(System)类加载器:也称为应用类加载器,它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类,是java.lang.ClassLoader类的子类。
父子加载器并非继承关系,也就是说子加载器不一定是继承了父加载器。

对于Java来说,java 虚拟机要将被用到的java类文件通过classLoader 加载到JVM内存中,而这个classloader就是bootstrap classloader。而对于APP而言,首先请求app 级来加载,继而请求extension classLoader,最后启动bootstrap classLoader 。

自定义ClassLoader

这里写图片描述

public class MyClassLoader extends ClassLoader {

    //类加载器名称
    private String name;
    //加载类的路径
    private String path = "D:/";
    private final String fileType = ".class";
    public MyClassLoader(String name){
        //让系统类加载器成为该 类加载器的父加载器
        super();
        this.name = name;
    }

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

    public String getPath() {
        return path;
    }

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

    @Override
    public String toString() {
        return this.name;
    }

    private byte[] loaderClassData(String name){
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        this.name = this.name.replace(".", "/");
        try {
            is = new FileInputStream(new File(path + name + fileType));
            int c = 0;
            while(-1 != (c = is.read())){
                baos.write(c);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    @Override
    public Class<?> findClass(String name){
        byte[] data = loaderClassData(name);
        return this.defineClass(name, data, 0, data.length);
    }

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        //loader1的父加载器为系统类加载器
        MyClassLoader loader1 = new MyClassLoader("loader1");
        loader1.setPath("D:/lib1/");
        //loader2的父加载器为loader1
        MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
        loader2.setPath("D:/lib2/");
        //loader3的父加载器为根类加载器
        MyClassLoader loader3 = new MyClassLoader(null, "loader3");
        loader3.setPath("D:/lib3/");

        Class clazz = loader2.loadClass("Sample");
        Object object = clazz.newInstance();
    }
}
public class Sample {

    public Sample(){
        System.out.println("Sample is loaded by " + this.getClass().getClassLoader());
        new A();
    }
}
public class A {

    public A(){
        System.out.println("A is loaded by " + this.getClass().getClassLoader());
    }
}

每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每个ClassLoader都会有一个parent ClassLoader,我们可以看一下ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前 ClassLoader的parent,注意,这个parent不是指的被继承的类,而是在实例化该ClassLoader时指定的一个 ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrap classloade。

上面讲解了一下ClassLoader的作用以及一个最基本的加载流程,接下来我们说说ClassLoader使用了双亲委托模式进行类加载。

ClassLoader

双亲委托模式

通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

为了更好的理解双亲委托模式,我们先自定义一个ClassLoader,假设我们使用这个自定义的ClassLoader加载 java.lang.String,那么这里String是否会被这个ClassLoader加载呢?

事实上java.lang.String这个类并不会被我们自定义的classloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先 委托它的父亲ClassLoader进行加载,只有当父亲ClassLoader无法加载成功后,才会由自己加载。而在上面的例子中,因为 java.lang.String是属于java核心API的一个类,所以当使用自定义的classloader加载它的时候,该 ClassLoader会先委托它的父亲ClassLoader进行加载(bootstrap classloader),所以并不会被我们自定义的ClassLoader加载。

我们来看一下ClassLoader的一段源码:

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{  
         // 首先检查该name指定的class是否有被加载  
         Class c = findLoadedClass(name);  
         if (c == null) {  
             try {  
                 if (parent != null) {  
                     //如果parent不为null,则调用parent的loadClass进行加载  
                     c = parent.loadClass(name, false);  
                 }else{  
                     //parent为null,则调用BootstrapClassLoader进行加载  
                     c = findBootstrapClass0(name);  
                 }  
             }catch(ClassNotFoundException e) {  
                 //如果仍然无法加载成功,则调用自身的findClass进行加载              
                 c = findClass(name);  
             }  
         }  
         if (resolve) {  
             resolveClass(c);  
         }  
         return c;  
    }

使用双亲委托模式优点

那么我们使用双亲委托模式有什么好处呢?

  1. 因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
  2. 考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时被加载,所以用户自定义类是无法加载一个自定义的ClassLoader。

附:Android ClassLoader简介