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

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

相关文章

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python进行JSON和Excel文件转换处理指南

《Python进行JSON和Excel文件转换处理指南》在数据交换与系统集成中,JSON与Excel是两种极为常见的数据格式,本文将介绍如何使用Python实现将JSON转换为格式化的Excel文件,... 目录将 jsON 导入为格式化 Excel将 Excel 导出为结构化 JSON处理嵌套 JSON:

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

Java堆转储文件之1.6G大文件处理完整指南

《Java堆转储文件之1.6G大文件处理完整指南》堆转储文件是优化、分析内存消耗的重要工具,:本文主要介绍Java堆转储文件之1.6G大文件处理的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言文件为什么这么大?如何处理这个文件?分析文件内容(推荐)删除文件(如果不需要)查看错误来源如何避

使用Python构建一个高效的日志处理系统

《使用Python构建一个高效的日志处理系统》这篇文章主要为大家详细讲解了如何使用Python开发一个专业的日志分析工具,能够自动化处理、分析和可视化各类日志文件,大幅提升运维效率,需要的可以了解下... 目录环境准备工具功能概述完整代码实现代码深度解析1. 类设计与初始化2. 日志解析核心逻辑3. 文件处

Java docx4j高效处理Word文档的实战指南

《Javadocx4j高效处理Word文档的实战指南》对于需要在Java应用程序中生成、修改或处理Word文档的开发者来说,docx4j是一个强大而专业的选择,下面我们就来看看docx4j的具体使用... 目录引言一、环境准备与基础配置1.1 Maven依赖配置1.2 初始化测试类二、增强版文档操作示例2.

MyBatis-Plus通用中等、大量数据分批查询和处理方法

《MyBatis-Plus通用中等、大量数据分批查询和处理方法》文章介绍MyBatis-Plus分页查询处理,通过函数式接口与Lambda表达式实现通用逻辑,方法抽象但功能强大,建议扩展分批处理及流式... 目录函数式接口获取分页数据接口数据处理接口通用逻辑工具类使用方法简单查询自定义查询方法总结函数式接口

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

Python使用vllm处理多模态数据的预处理技巧

《Python使用vllm处理多模态数据的预处理技巧》本文深入探讨了在Python环境下使用vLLM处理多模态数据的预处理技巧,我们将从基础概念出发,详细讲解文本、图像、音频等多模态数据的预处理方法,... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核