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

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

相关文章

电脑提示xlstat4.dll丢失怎么修复? xlstat4.dll文件丢失处理办法

《电脑提示xlstat4.dll丢失怎么修复?xlstat4.dll文件丢失处理办法》长时间使用电脑,大家多少都会遇到类似dll文件丢失的情况,不过,解决这一问题其实并不复杂,下面我们就来看看xls... 在Windows操作系统中,xlstat4.dll是一个重要的动态链接库文件,通常用于支持各种应用程序

SQL Server数据库死锁处理超详细攻略

《SQLServer数据库死锁处理超详细攻略》SQLServer作为主流数据库管理系统,在高并发场景下可能面临死锁问题,影响系统性能和稳定性,这篇文章主要给大家介绍了关于SQLServer数据库死... 目录一、引言二、查询 Sqlserver 中造成死锁的 SPID三、用内置函数查询执行信息1. sp_w

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

Golang 日志处理和正则处理的操作方法

《Golang日志处理和正则处理的操作方法》:本文主要介绍Golang日志处理和正则处理的操作方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录1、logx日志处理1.1、logx简介1.2、日志初始化与配置1.3、常用方法1.4、配合defer

springboot加载不到nacos配置中心的配置问题处理

《springboot加载不到nacos配置中心的配置问题处理》:本文主要介绍springboot加载不到nacos配置中心的配置问题处理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录springboot加载不到nacos配置中心的配置两种可能Spring Boot 版本Nacos

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

SpringBoot整合Sa-Token实现RBAC权限模型的过程解析

《SpringBoot整合Sa-Token实现RBAC权限模型的过程解析》:本文主要介绍SpringBoot整合Sa-Token实现RBAC权限模型的过程解析,本文给大家介绍的非常详细,对大家的学... 目录前言一、基础概念1.1 RBAC模型核心概念1.2 Sa-Token核心功能1.3 环境准备二、表结

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

Python处理大量Excel文件的十个技巧分享

《Python处理大量Excel文件的十个技巧分享》每天被大量Excel文件折磨的你看过来!这是一份Python程序员整理的实用技巧,不说废话,直接上干货,文章通过代码示例讲解的非常详细,需要的朋友可... 目录一、批量读取多个Excel文件二、选择性读取工作表和列三、自动调整格式和样式四、智能数据清洗五、

SpringBoot如何对密码等敏感信息进行脱敏处理

《SpringBoot如何对密码等敏感信息进行脱敏处理》这篇文章主要为大家详细介绍了SpringBoot对密码等敏感信息进行脱敏处理的几个常用方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录​1. 配置文件敏感信息脱敏​​2. 日志脱敏​​3. API响应脱敏​​4. 其他注意事项​​总结