当前位置: 首页 > news >正文

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

相关文章:

  • 手机号中间四位加星号
  • UISearchBar使用及修改样式
  • 关于api-ms-win-crt-runtimel1-1-0.dll缺失的解决方案
  • Android 高德地图入门详解
  • JavaScript操作符
  • Runtime学习笔记
  • windows 下 TCP 端口转发
  • 2017大数据标准化论坛发布了第一批大数据系统测试结果,阿里云数加获得了大数据系统测试证书。...
  • idea java.lang.OutOfMemoryError: PermGen space
  • uml序列图
  • idea拉取SVN maven 多模块项目
  • 通过a标签直接下载图片或文本文档
  • 软件测试计划
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • StreamingPro 支持Spark Structured Streaming
  • 【Leetcode】101. 对称二叉树
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • Apache Pulsar 2.1 重磅发布
  • FineReport中如何实现自动滚屏效果
  • golang 发送GET和POST示例
  • HTTP请求重发
  • IDEA 插件开发入门教程
  • Java比较器对数组,集合排序
  • Spark学习笔记之相关记录
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • tab.js分享及浏览器兼容性问题汇总
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 目录与文件属性:编写ls
  • 前端工程化(Gulp、Webpack)-webpack
  • 通过git安装npm私有模块
  • 用Canvas画一棵二叉树
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • 通过调用文摘列表API获取文摘
  • # MySQL server 层和存储引擎层是怎么交互数据的?
  • #if 1...#endif
  • #pragma once
  • (+3)1.3敏捷宣言与敏捷过程的特点
  • (C语言)逆序输出字符串
  • (windows2012共享文件夹和防火墙设置
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (一) storm的集群安装与配置
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • (转)h264中avc和flv数据的解析
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (转)项目管理杂谈-我所期望的新人
  • (转载)Google Chrome调试JS
  • .Net Web窗口页属性
  • .Net 代码性能 - (1)
  • .net 受管制代码
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • /usr/local/nginx/logs/nginx.pid failed (2: No such file or directory)