阿里二面:双亲委派机制?原理?能打破吗?

1、什么是双亲委派机制

双亲委派机制(Parent Delegation Mechanism)是Java中的一种类加载机制。在Java中,类加载器负责加载类的字节码并创建对应的Class对象。双亲委派机制是指当一个类加载器收到类加载请求时,它会先将该请求委派给它的父类加载器去尝试加载。只有当父类加载器无法加载该类时,子类加载器才会尝试加载。

这种机制的设计目的是为了保证类的加载是有序的,避免重复加载同一个类。Java中的类加载器形成了一个层次结构,根加载器(Bootstrap ClassLoader)位于最顶层,它负责加载Java核心类库。其他加载器如扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)都有各自的加载范围和职责。通过双亲委派机制,可以确保类在被加载时,先从上层的加载器开始查找,逐级向下,直到找到所需的类或者无法找到为止。

这种机制的好处是可以避免类的重复加载,提高了类加载的效率和安全性。同时,它也为Java提供了一种扩展机制,允许开发人员自定义类加载器,实现特定的加载策略。

图片

另外:
推荐一个程序员免费学习的编程网站:我爱编程网(www.love-coding.com
涵盖 Java几乎覆盖了所有主流技术面试题,还有市面上最全的技术精品系列教程,免费提供。
在这里插入图片描述

2、双亲委派机制的优缺点

优点:

  1. 避免重复加载:通过委派给父类加载器,可以避免同一个类被多次加载,提高了加载效率。
  2. 安全性:通过双亲委派机制,核心类库由根加载器加载,可以确保核心类库的安全性,防止恶意代码替换核心类。
  3. 扩展性:开发人员可以自定义类加载器,实现特定的加载策略,从而扩展Java的类加载机制。

缺点:

  1. 灵活性受限:双亲委派机制对于某些特殊的类加载需求可能过于严格,限制了加载器的灵活性。
  2. 破坏隔离性:如果自定义类加载器不遵循双亲委派机制,可能会破坏类加载的隔离性,导致类冲突或安全性问题。
  3. 不适合动态更新:由于类加载器在加载类时会先检查父加载器是否已加载,因此在动态更新类时可能会出现问题,需要额外的处理。

总体而言,双亲委派机制通过层次结构和委派机制提供了一种有序、安全的类加载方式,但也存在一些限制和不适用的情况。

3、双亲委派机制的应用场景

双亲委派机制在Java中有多种应用场景。一些常见的应用场景包括:

  1. 类加载:双亲委派机制主要用于Java中的类加载。它确保类以层次结构方式加载,从引导类加载器开始,然后是扩展类加载器,最后是应用程序类加载器。这样可以高效且一致地加载类,跨类加载器层次结构。
  2. 安全性:双亲委派机制在确保Java应用程序的安全性方面起着重要作用。通过委派类加载给父加载器,它可以防止未经授权或恶意代码的加载和执行。这有助于维护Java运行时环境的完整性和安全性。
  3. 类库:双亲委派机制对于加载和管理类库非常有用。引导类加载器位于层次结构的顶部,负责加载核心Java类。扩展类加载器负责从Java扩展目录加载类。应用程序类加载器负责从应用程序的类路径加载类。这种分离允许高效组织和管理类库。
  4. 自定义类加载:开发人员可以利用双亲委派机制实现具有特定加载行为的自定义类加载器。通过扩展现有的类加载器并重写类加载过程,开发人员可以引入自定义逻辑,从非标准位置加载类或对加载的类应用自定义转换。

双亲委派机制在Java中广泛应用于类加载、安全性强制执行和类库管理等方面,为Java应用程序提供了结构化和安全的环境。

4、双亲委派机制的原理

双亲委派机制是Java中的一种类加载机制。其原理如下:

  1. 当Java程序需要加载一个类时,首先会委托给当前类加载器的父类加载器进行加载。
  2. 父类加载器会按照相同的方式尝试加载该类。如果父类加载器能够成功加载该类,则加载过程结束。
  3. 如果父类加载器无法加载该类,则会将加载请求再次委托给它的父类加载器,直到达到顶层的引导类加载器。
  4. 引导类加载器是Java虚拟机内置的类加载器,它负责加载核心类库,如java.lang包下的类。
  5. 如果引导类加载器也无法加载该类,则会回到初始的类加载器,尝试使用自身的加载机制加载该类。
  6. 如果自身的加载机制仍然无法加载该类,则会抛出ClassNotFoundException异常。

通过这种双亲委派的机制,Java实现了类加载的层次结构。它可以确保类的加载是有序的,避免了重复加载和类的冲突。同时,它也提供了一种安全机制,防止恶意代码的加载和执行。

5、通过代码讲解双亲委派机制

首先,我们需要自定义一个类加载器,继承自ClassLoader类,并重写loadClass()方法。在loadClass()方法中,我们可以实现自己的类加载逻辑。

package com.pany.camp.classloader;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

/**
* @description: 双亲委派机制
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-30 8:41
*/
public class MyClassLoader extends ClassLoader {
  @Override
  public Class<?> loadClass(String name) throws ClassNotFoundException {
      // 首先尝试使用父类加载器加载类
      try {
          return super.loadClass(name);
      } catch (ClassNotFoundException e) {
          // 如果父类加载器无法加载类,则自己加载类
          return findClass(name);
      }
  }

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
      // 在这里实现自己的类加载逻辑,例如从特定位置加载类文件
      // 这里只是一个示例,实际实现需要根据具体需求进行处理
      byte[] classData = loadClassData(name);
      if (classData == null) {
          throw new ClassNotFoundException();
      }
      return defineClass(name, classData, 0, classData.length);
  }

  private byte[] loadClassData(String name) {
      // 在这里实现加载类文件的逻辑,返回类文件的字节数组
      // 这里只是一个示例,实际实现需要根据具体需求进行处理
      try {
          FileInputStream fis = new FileInputStream(name + ".class");
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
          byte[] buffer = new byte[1024];
          int len;
          while ((len = fis.read(buffer)) != -1) {
              bos.write(buffer, 0, len);
          }
          fis.close();
          bos.close();
          return bos.toByteArray();
      } catch (IOException e) {
          e.printStackTrace();
      }
      return null;
  }

  public static void main(String[] args) {
      // 创建自定义类加载器
      MyClassLoader classLoader = new MyClassLoader();

      try {
          // 使用自定义类加载器
          Class<?> clazz = classLoader.loadClass("com.pany.camp.classloader.MyClassLoader");
          System.out.println("Class loaded: " + clazz.getName());
      } catch (ClassNotFoundException e) {
          System.out.println("Class not found");
      }
  }
}

我们创建了一个自定义的类加载器MyClassLoader,并使用它来加载名为"com.example.MyClass"的类。在loadClass()方法中,我们首先尝试使用父类加载器加载类,如果父类加载器无法加载类,则自己加载类。

通过运行上面代码,我们可以观察到双亲委派机制的工作原理。首先,自定义类加载器会尝试委托给父类加载器加载类,如果父类加载器能够成功加载类,则加载过程结束。如果父类加载器无法加载类,则会回到自己的加载机制,尝试使用自身的类加载逻辑加载类。

6、打破双亲委派机制

首先,我们需要自定义一个类加载器,继承自ClassLoader类,并重写loadClass()方法。在loadClass()方法中,我们可以实现自己的类加载逻辑。

6.1 为什么要打破双亲委派机制吗?

打破双亲委派机制,就像是在Java世界的稳固城墙上敲开一扇门,让新鲜空气和新思想涌入。这一机制的打破,通常出于以下几个原因:

1. 热部署的梦想

想象一下,你正在开发一个大型软件系统,每次更新都需要重启整个应用,这不仅耗时耗力,还可能导致服务中断。为了让系统能够不停机更新,开发者们梦想着实现类的热部署——在运行时动态加载和替换类,而不需要重启应用。双亲委派机制像一位守旧的家长,坚持类的加载只能发生一次。为了打破这一限制,开发者们决定自定义类加载器,让类的热替换成为可能。

2. 非标准类文件的探险

在Java的广阔世界中,总有一些探险家想要探索未知的领域。他们遇到了一些特殊的类文件,比如动态生成的字节码或非标准的类文件格式。这些文件像是外星语言,标准的类加载器无法理解。为了解码这些神秘的信息,开发者们决定打破双亲委派机制,自定义类加载器,以实现对这些非标准类文件的加载和解析。

3. 类加载动态控制的魔法

还有一些开发者,他们像是掌握了魔法的魔法师,需要对类的加载进行特殊的控制。他们可能需要对特定的类进行加密、解密或验证等操作。这些特殊的需求,双亲委派机制无法满足。因此,他们决定打破这一机制,自定义类加载器,在加载类时施展他们的魔法。

4. 框架和容器的独立性

在JavaEE容器(如Tomcat、WebLogic等)中,通常会使用自定义的类加载器来加载应用程序的类,以实现应用程序的隔离和独立性。这些容器需要打破双亲委派机制,使用自定义的类加载器来加载应用程序的类,确保不同应用之间的类不会相互干扰。

虽然打破双亲委派机制可以带来上述的好处,但它也可能引入一些潜在的风险和问题。类的冲突、不一致性等问题可能会像不请自来的客人,给Java世界带来混乱。因此,当开发者们决定打破这一机制时,他们需要谨慎考虑,确保自定义的类加载器能够正确处理类的加载和依赖关系,维护Java世界的和谐与秩序。

6.2 怎样打破双亲委派机制?

在Java中,列举了几种方法可以打破双亲委派机制:

1.自定义类加载器

当两个类的全限定名相同时,就不可以同时加载,入tomcat运行两个web服务时就会出现冲突(相同类名),不够tomcat通过为每一个应用分配一个应用类加载器,打破双亲委派机制。

自定义实现(1)类继承ClassLoader类,重写loadClass方法(2)loadClass中获取被加载类数据(loadClassData)(3)调用defineClass完成类加载注:加载类的时候会要求所有类都继承父类(默认object类),不然会报错,可以将待加载类同目录放入父类,也可直接将父类交由ClassLoader中的类加载器加载,在一个Java虚拟机中,只有同一个类加载器外加相同的类限定名才会认为冲突

2.线程上下文类加载器

通过Thread类的setContextClassLoader()方法,可以设置线程的上下文类加载器。在某些框架或库中,会使用线程上下文类加载器来加载特定的类,从而打破双亲委派机制。

3.Java SPI机制

Java SPI(Service Provider Interface)是一种标准的服务发现机制,在SPI中,服务的实现类通过在META-INF/services目录下的配置文件中声明,而不是通过类路径来查找。通过SPI机制,可以实现在不同的类加载器中加载不同的服务实现类,从而打破双亲委派机制。

4.osgi框架类加载器(不再使用)

自己实现了一套加载机制,实现了类的热部署,现在可用arthas实线终:打破双亲委派机制的唯一方式就是重写ClassLoader。

总结

双亲委派机制是Java类加载器的一种工作机制,它的核心思想是在类加载的过程中,优先将加载请求委派给父类加载器,只有在父类加载器无法加载时,才由子类加载器尝试加载。

双亲委派机制的主要特点和优势包括:

  • 避免类的重复加载:当一个类被加载后,它会被父类加载器缓存起来,避免了重复加载同一个类的问题,提高了类加载的效率。
  • 类的隔离和安全性:通过双亲委派机制,不同的类加载器加载的类具有不同的命名空间,相同类名的类可以被不同的类加载器加载,实现了类的隔离和安全性。
  • 保护核心类库的完整性:核心类库由启动类加载器加载,避免了用户自定义的类替换核心类库的情况,保护了核心类库的完整性。

总结起来:

  1. 双亲委派机制通过层级结构的类加载器组织,实现了类的共享、隔离和安全性。
  2. 它是Java类加载器的一种重要机制,为Java应用程序提供了良好的类加载环境。
  3. 然而,在某些特定的场景下,为了满足特定的需求,可能需要打破双亲委派机制,使用自定义的类加载器来加载类。
  4. 在使用自定义类加载器时,需要仔细评估和测试,确保能够正确处理类的加载和依赖关系。
### IDS国际数据空间项目简介 IDS(International Data Spaces,国际数据空间)是一个旨在促进安全、可信的数据交换和共享的框架[^1]。它通过定义标准化的技术组件和服务来实现跨组织间高效协作的目标。核心目标在于使不同企业能够在保护各自数据主权的前提下自由地进行数据交互。 #### 技术特点 - **数据主权保障**:IDS连接器允许数据所有者完全掌控自己的数据流向以及访问权限设置。 - **灵活性高**:不仅适用于结构化数据(如测量记录、产品规格),还兼容非结构化的流媒体等形式的内容传输需求。 - **互操作性强**:遵循统一标准接口设计原则,便于第三方开发者集成扩展功能模块。 对于希望加入该项目的研究人员或者工程师来说,可以从以下几个方面入手: 1. **学习官方文档资料** 访问官方网站获取最新版本的手册指南和技术白皮书文件,深入理解整个体系架构设计理念及其运作原理。 2. **安装部署测试环境** 下载开源软件包并按照说明完成本地实例搭建工作,在实际动手过程中熟悉各个组成部分的功能作用。 3. **贡献代码或反馈意见** 如果具备一定编程能力,则可以通过GitHub仓库提交补丁修复已知缺陷;即使不具备编码技能,也可以积极参与社区讨论区留言分享使用心得体验报告等信息帮助改进产品质量。 以下是关于如何具体实施上述建议的一些实用技巧提示: ```bash # 克隆IDSA GitHub存储库至本机目录下 git clone https://github.com/InternationalDataSpacesAssociation/idsa.git cd idsa pip install -r requirements.txt python manage.py runserver ``` 以上命令序列展示了怎样快速启动一个基本的服务端程序运行状态供进一步探索研究之用。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

首席架构师专栏

喜欢请点赞,么么哒

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值