android classloader双亲委托模式详解手机开发

概述

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简介

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/5865.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论