需要上级先处理的双亲委派模型

2023-11-23 16:10

本文主要是介绍需要上级先处理的双亲委派模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

大纲

文章目录

  • 大纲
  • 前言
  • 类加载器
    • 类相等条件
    • 类加载器分类
  • 双亲委派模型
    • 工作过程
    • 好处
    • 实现
  • 破坏双亲委派
    • 第一次
    • 第二次
    • 第三次
  • 可参考的案例

前言


我的所有文章同步更新与Github–Java-Notes,想了解JVM,HashMap源码分析,spring相关,剑指offer题解(Java版),可以点个star。可以看我的github主页,每天都在更新哟。

邀请您跟我一同完成 repo


类加载器是面试的高频题,类加载器的双亲委派模型更是重中之重,基本问到类加载器就会问到双亲委派模型,那么他到底是什么,又有什么好处呢

我们先来个模拟个这样的情景:

我们打仗的时候,另外一个陌生的队伍让你调兵支援,你是自作主张,还是向上级汇报,让上级处理,请求上级指示,按照指示行动呢?

这个场景跟双亲委派的做法有一点相似,你可以考虑下那种做法比较好,又有什么好处。

类加载器

上一节我们知道了类加载的过程,其中加载阶段的第一步"通过一个类的全限定名来获取描述此类的二进制字节流"这个动作放到虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。这个过程的代码模块就是类加载器

类相等条件

  • 同一个类文件
  • 同一个虚拟机加载
  • 同一个类加载器加载
  • 其中一个不同,这两个类就不相等

类加载器分类

  • 启动类加载器(Bootstrap ClassLoader)
    • 将放在<JAVA_HOME>\lib目录中的或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存(注意三个关键词)
    • 启动类加载器无法被Java程序直接引用
  • 扩展类加载器(Extension ClassLoader)
    • 负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有库
    • 开发者可以直接使用这个类加载器
  • 应用程序类加载器(Application ClassLoader)
    • 负责加载用户类路径上所指定的类库
    • 开发者可以直接使用
    • 如果应用程序中没有定义过自己的类加载器,一般情况下默认的类加载器
  • 用户自定义类加载器

如果划分的粗一点,那么只用两种

  • 启动类加载器
    • 在HotSpot中(其他虚拟机可能不一样),这个是由C++编写的,是虚拟机的一部分
  • 其他类加载器
    • 由Java编写。
    • 独立于虚拟机外部
    • 全部继承自java.lang.ClassLoader

双亲委派模型

上面我们已经介绍了类加载器,大致可以分为四种:

  • 启动类加载器
  • 扩展类加载器
  • 应用程序类加载器
  • 用户自定义的类加载器

他们是相互配合工作的,如果类加载之间的层次是下图这种关系,那么就是双亲委派模型

双亲委派模型并不是强制性的约束模型,他只是一个Java设计者推荐的实现方式

工作过程

  • 一个类加载器收到了类加载的请求
  • 他不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成
  • 每一个类加载器都是如此
  • 当父类加载器完不成加载动作,才让子类加载器自己去加载

好比这样的情景:

我们打仗的时候,另外一个陌生的队伍让你调兵支援,这个时候你不能擅作主张,要向上级请示,让上级来进行协调工作,做决定。上级做不了决定,你再尝试随机应变,结合实际情况做决定。

那么你结合情景想想,这样做的好处是啥呢?

好处

不会无组织无纪律。

没出问题还好,要是遇到这样的情况,那就真的危险了。假如你不向上级请示,擅作主张,让你增援的那个部队是敌人的,布好阵,就埋伏你,这就要丢性命啊。

或者是上级让你增援另一个部队,因为你离得最近,但是他不知道你不在那个位置,等你接到命令再赶到,友军尸体都凉了


对应到Java中,就是保证了运行环境不会混乱

因为Java类随着他的类加载器一起具备了一种带有优先级的层次关系

例如所有类的父类(除了它本身)“java.lang.Object”,有了双亲委派,无论哪一个类加载器加载,都是交给最顶层的启动类加载器加载,这样他在任何类加载器下都是那一个Object类。

如果没有双亲委派,我自己定义一个Object类,放到程序的ClassPath中,那么系统就会出现多个Object,那么Java体系最基础的行为就都无法保证了。

实现

双亲委派的实现非常简单

  • 先检查这个类是否已经被加载过
  • 如果没有,调用父类加载器的 loadClass()方法
  • 如果父类加载器为空,则调用启动类加载器作为父类加载器
  • 如果父类加载失败
    • 抛出异常,但是不进行处理
    • 调用自己的findClass()方法加载
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> 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);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

破坏双亲委派

我们之前说到,双亲委派模型只是一个推荐模型,并不是强制约束,既然是推荐,那我可以不听啊。我就是要破坏,不过这里的破坏并不是贬义的,而是对其进行创新。一共大概有三次较大规模的"不听"

  • 双亲委派出现后为了保证向前兼容
  • 自身设计缺陷
  • 用户追求程序动态性

第一次

双亲委派模型是JDK1.2之后才有的,但是抽象类java.lang.ClassLoader是1.0 就存在了的,面对已经存在的用户自定义类加载器的实现代码,Java设计者不得不做出一些妥协。java.lang.ClassLoader中添加了一个findClass方法,原来用户需要继承java.lang.ClassLoader类,然后重写loadClass方法,现在只需要重写findClass,把自己实现的加载逻辑放到这个方法里,而不需要重写loadClass方法。

如果父类加载失败,则调用自己的findClass方法完成加载,这样就可以保证写出来的加载器的逻辑仍是符合双亲委派的。

第二次

双亲委派很好的解决了个各类加载器基础类的同一问题,但是用户又想调用用户自己的代码怎么办

比如我们的JDBC,是各个厂商自己独立实现的,Java只是提供一个接口,其他厂商自己独立实现,但是启动类加载器可不认识这个东西。

所以Java设计者引入了一个不太优雅的设计:线程上下文类加载器

这个类加载器通过java.lang.Thread类中的setContextClassLoader()方法进行设置,如果创建线程时还未设置,他将会从父类线程中继承一个,如果在应用程序的全局范围内都没有设置过,那这个类加载器默认就是应用程序类加载器

/*** Sets the context ClassLoader for this Thread. The context* ClassLoader can be set when a thread is created, and allows* the creator of the thread to provide the appropriate class loader,* through {@code getContextClassLoader}, to code running in the thread* when loading classes and resources.** <p>If a security manager is present, its {@link* SecurityManager#checkPermission(java.security.Permission) checkPermission}* method is invoked with a {@link RuntimePermission RuntimePermission}{@code* ("setContextClassLoader")} permission to see if setting the context* ClassLoader is permitted.** @param  cl*         the context ClassLoader for this Thread, or null  indicating the*         system class loader (or, failing that, the bootstrap class loader)** @throws  SecurityException*          if the current thread cannot set the context ClassLoader** @since 1.2*/
public void setContextClassLoader(ClassLoader cl) {SecurityManager sm = System.getSecurityManager();if (sm != null) {sm.checkPermission(new RuntimePermission("setContextClassLoader"));}contextClassLoader = cl;
}

有了这个线程上下文类加载器,JDBC服务使用这个线程上下文类加载器去加载所需要的SPI(Service Provider Interface,接口提供者)代码,也就是父类加载器请子类加载器去完成类加载的动作。这样的行为实际是打通了双亲委派模型的层次结构来逆向使用类加载器,违背了双亲委派的一般性原则。

Java中涉及SPI的加载基本都采用这种方式,如JNDI、JDBC、JCE和JBI

第三次

用户对程序动态性的追求而导致的。

动态性:指的是当前一些非常热门的名词:代码热替换,模块热部署等等

简单理解就是希望程序像计算机外设那样,接上鼠标、U盘,不用重启就能立即使用,鼠标有问题要换一个,也不需要关机、重启。这样的热部署对企业级软件开发者有很大的吸引力

像OSGi这种实现模块化热部署,已经算是无冕之王了,业界内的Java模块化标准

他实现的关键是它自定义的类加载器机制

  • 每一个程序模块(Bundle)都有一个自己的类加载器
  • 当需要更换一个Bundle时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换

OSGi下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构,当收到类加载请求时,OSGi将按照下面的顺序进行类搜索

  1. 将以java.*开头的类委派给父类加载器加载
  2. 否则,将委派列表名单内的类委派给父类加载器加载
  3. 否则,将Import列表中的类委派给Export这个类的Bundle的类加载器加载
  4. 否则,查找当前Bundle的ClassPath,使用自己的类加载器加载
  5. 否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载
  6. 否则,查找Dynamic Import列表的Bundle,委派给对应的Bundle的类加载器加载
  7. 否则,类查找失败

可参考的案例

可以根据实际项目来理解双亲委派模型,可以参考我下面的博文

比较标准的双亲委派模型——tomcat

异类——OSGi

这篇关于需要上级先处理的双亲委派模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/419123

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python实现自动化接收与处理手机验证码

《Python实现自动化接收与处理手机验证码》在移动互联网时代,短信验证码已成为身份验证、账号注册等环节的重要安全手段,本文将介绍如何利用Python实现验证码的自动接收,识别与转发,需要的可以参考下... 目录引言一、准备工作1.1 硬件与软件需求1.2 环境配置二、核心功能实现2.1 短信监听与获取2.

Python使用date模块进行日期处理的终极指南

《Python使用date模块进行日期处理的终极指南》在处理与时间相关的数据时,Python的date模块是开发者最趁手的工具之一,本文将用通俗的语言,结合真实案例,带您掌握date模块的六大核心功能... 目录引言一、date模块的核心功能1.1 日期表示1.2 日期计算1.3 日期比较二、六大常用方法详

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir