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

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

相关文章

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Python结合requests和Cheerio处理网页内容的操作步骤

《Python结合requests和Cheerio处理网页内容的操作步骤》Python因其简洁明了的语法和强大的库支持,成为了编写爬虫程序的首选语言之一,requests库是Python中用于发送HT... 目录一、前言二、环境搭建三、requests库的基本使用四、Cheerio库的基本使用五、结合req

使用Python处理CSV和Excel文件的操作方法

《使用Python处理CSV和Excel文件的操作方法》在数据分析、自动化和日常开发中,CSV和Excel文件是非常常见的数据存储格式,ython提供了强大的工具来读取、编辑和保存这两种文件,满足从基... 目录1. CSV 文件概述和处理方法1.1 CSV 文件格式的基本介绍1.2 使用 python 内

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超