返回顶部
首页 > 资讯 > 精选 >Java中怎么实现类隔离加载
  • 324
分享到

Java中怎么实现类隔离加载

2023-06-15 20:06:13 324人浏览 安东尼
摘要

Java中怎么实现类隔离加载,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一 什么是类隔离技术只要你 Java 代码写的足够多,就一定会出现这种情况:系统新引入了一个中间件

Java中怎么实现类隔离加载,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

一 什么是类隔离技术

只要你 Java 代码写的足够多,就一定会出现这种情况:系统新引入了一个中间件jar  包,编译的时候一切正常,一运行就报错:java.lang.NoSuchMethodError,然后就哼哧哼哧的开始找解决方法,最后在几百个依赖包里面找的眼睛都快瞎了才找到冲突的  jar,把问题解决之后就开始吐槽中间件为啥搞那么多不同版本的 jar,写代码五分钟,排包排了一整天。

上面这种情况就是 Java 开发过程中常见的情况,原因也很简单,不同 jar 包依赖了某些通用 jar  包(如日志组件)的版本不一样,编译的时候没问题,到了运行时就会因为加载的类跟预期不符合导致报错。举个例子:A 和 B 分别依赖了 C 的 v1 和 v2  版本,v2 版本的 Log 类比 v1 版本新增了 error 方法,现在工程里面同时引入了 A、B 两个 jar 包,以及 C 的 v0.1、v0.2  版本,打包的时候 Maven 只能选择一个 C 的版本,假设选择了 v1  版本。到了运行的时候,默认情况下一个项目的所有类都是用同一个类加载器加载的,所以不管你依赖了多少个版本的 C,最终只会有一个版本的 C 被加载到 JVM 中。当  B 要去访问 Log.error,就会发现 Log 压根就没有 error  方法,然后就抛异常java.lang.NoSuchMethodError。这就是类冲突的一个典型案例。

Java中怎么实现类隔离加载

类冲突的问题如果版本是向下兼容的其实很好解决,把低版本的排除掉就完事了。但要是遇到版本不向下兼容的那就陷入了“救妈妈还是救女朋友”的两难处境了。

为了避免两难选择,有人就提出了类隔离技术来解决类冲突的问题。类隔离的原理也很简单,就是让每个模块使用独立的类加载器来加载,这样不同模块之间的依赖就不会互相影响。如下图所示,不同的模块用不同的类加载器加载。为什么这样做就能解决类冲突呢?这里用到了  Java 的一个机制:不同类加载器加载的类在 JVM 看来是两个不同的类,因为在 JVM 中一个类的唯一标识是 类加载器+类名。通过这种方式我们就能够同时加载  C 的两个不同版本的类,即使它类名是一样的。注意,这里类加载器指的是类加载器的实例,并不是一定要定义两个不同类加载器,例如图中的  PluginClassLoaderA 和 PluginClassLoaderB 可以是同一个类加载器的不同实例。

Java中怎么实现类隔离加载

二 如何实现类隔离

前面我们提到类隔离就是让不同模块的 jar 包用不同的类加载器加载,要做到这一点,就需要让 JVM  能够使用自定义的类加载器加载我们写的类以及其关联的类。

那么如何实现呢?一个很简单的做法就是 JVM  提供一个全局类加载器的设置接口,这样我们直接替换全局类加载器就行了,但是这样无法解决多个自定义类加载器同时存在的问题。

实际上 JVM 提供了一种非常简单有效的方式,我把它称为类加载传导规则:JVM 会选择当前类的类加载器来加载所有该类的引用的类。例如我们定义了 TestA  和 TestB 两个类,TestA 会引用 TestB,只要我们使用自定义的类加载器加载 TestA,那么在运行时,当 TestA 调用到 TestB  的时候,TestB 也会被 JVM 使用 TestA 的类加载器加载。依此类推,只要是 TestA 及其引用类关联的所有 jar  包的类都会被自定义类加载器加载。通过这种方式,我们只要让模块的 main 方法类使用不同的类加载器加载,那么每个模块的都会使用 main  方法类的类加载器加载的,这样就能让多个模块分别使用不同类加载器。这也是 OSGi 和 SofaArk 能够实现类隔离的核心原理。

了解了类隔离的实现原理之后,我们从重写类加载器开始进行实操。要实现自己的类加载器,首先让自定义的类加载器继承  java.lang.ClassLoader,然后重写类加载的方法,这里我们有两个选择,一个是重写 findClass(String name),一个是重写  loadClass(String name)。那么到底应该选择哪个?这两者有什么区别?

下面我们分别尝试重写这两个方法来实现自定义类加载器。

1 重写 findClass

首先我们定义两个类,TestA 会打印自己的类加载器,然后调用 TestB 打印它的类加载器,我们预期是实现重写了 findClass 方法的类加载器  MyClassLoaderParentFirst 能够在加载了 TestA 之后,让 TestB 也自动由 MyClassLoaderParentFirst  来进行加载。

public class TestA {      public static void main(String[] args) {         TestA testA = new TestA();         testA.hello();     }      public void hello() {         System.out.println("TestA: " + this.getClass().getClassLoader());         TestB testB = new TestB();         testB.hello();     } }  public class TestB {      public void hello() {         System.out.println("TestB: " + this.getClass().getClassLoader());     } }

然后重写一下 findClass 方法,这个方法先根据文件路径加载 class 文件,然后调用 defineClass 获取 Class 对象。

public class MyClassLoaderParentFirst extends ClassLoader{      private Map<String, String> classPathMap = new HashMap<>();      public MyClassLoaderParentFirst() {         classPathMap.put("com.java.loader.TestA", "/Users/hansong/ideaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestA.class");         classPathMap.put("com.java.loader.TestB", "/Users/hansong/IdeaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestB.class");     }      // 重写了 findClass 方法     @Override     public Class<?> findClass(String name) throws ClassNotFoundException {         String classPath = classPathMap.get(name);         File file = new File(classPath);         if (!file.exists()) {             throw new ClassNotFoundException();         }         byte[] classBytes = getClassData(file);         if (classBytes == null || classBytes.length == 0) {             throw new ClassNotFoundException();         }         return defineClass(classBytes, 0, classBytes.length);     }      private byte[] getClassData(File file) {         try (InputStream ins = new FileInputStream(file); ByteArrayOutputStream baos = new                 ByteArrayOutputStream()) {             byte[] buffer = new byte[4096];             int bytesNumRead = 0;             while ((bytesNumRead = ins.read(buffer)) != -1) {                 baos.write(buffer, 0, bytesNumRead);             }             return baos.toByteArray();         } catch (FileNotFoundException e) {             e.printStackTrace();         } catch (IOException e) {             e.printStackTrace();         }         return new byte[] {};     } }

最后写一个 main 方法调用自定义的类加载器加载 TestA,然后通过反射调用 TestA 的 main 方法打印类加载器的信息。

public class MyTest {      public static void main(String[] args) throws Exception {         MyClassLoaderParentFirst myClassLoaderParentFirst = new MyClassLoaderParentFirst();         Class testAClass = myClassLoaderParentFirst.findClass("com.java.loader.TestA");         Method mainMethod = testAClass.getDeclaredMethod("main", String[].class);         mainMethod.invoke(null, new Object[]{args});     }

执行的结果如下:

TestA: com.java.loader.MyClassLoaderParentFirst@1d44bcfa TestB: sun.misc.Launcher$AppClassLoader@18b4aac2

执行的结果并没有如我们期待,TestA 确实是 MyClassLoaderParentFirst 加载的,但是 TestB 还是  AppClassLoader 加载的。这是为什么呢?

要回答这个问题,首先是要了解一个类加载的规则:JVM 在触发类加载时调用的是 ClassLoader.loadClass  方法。这个方法的实现了双亲委派:

  • 委托给父加载器查询

  • 如果父加载器查询不到,就调用 findClass 方法进行加载

明白了这个规则之后,执行的结果的原因就找到了:JVM 确实使用了MyClassLoaderParentFirst 来加载  TestB,但是因为双亲委派的机制,TestB 被委托给了 MyClassLoaderParentFirst 的父加载器 AppClassLoader  进行加载。

你可能还好奇,为什么 MyClassLoaderParentFirst 的父加载器是 AppClassLoader?因为我们定义的 main  方法类默认情况下都是由 jdk 自带的 AppClassLoader 加载的,根据类加载传导规则,main 类引用的  MyClassLoaderParentFirst 也是由加载了 main 类的AppClassLoader 来加载。由于  MyClassLoaderParentFirst 的父类是 ClassLoader,ClassLoader 的默认构造方法会自动设置父加载器的值为  AppClassLoader。

protected ClassLoader() {     this(checkCreateClassLoader(), getSystemClassLoader()); }

2 重写 loadClass

由于重写 findClass 方法会受到双亲委派机制的影响导致 TestB 被 AppClassLoader 加载,不符合类隔离的目标,所以我们只能重写  loadClass 方法来破坏双亲委派机制。代码如下所示:

public class MyClassLoaderCustom extends ClassLoader {      private ClassLoader jdkClassLoader;      private Map<String, String> classPathMap = new HashMap<>();      public MyClassLoaderCustom(ClassLoader jdkClassLoader) {         this.jdkClassLoader = jdkClassLoader;         classPathMap.put("com.java.loader.TestA", "/Users/hansong/IdeaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestA.class");         classPathMap.put("com.java.loader.TestB", "/Users/hansong/IdeaProjects/OhMyJava/CodeRepository/target/classes/com/java/loader/TestB.class");     }      @Override     protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {         Class result = null;         try {             //这里要使用 JDK 的类加载器加载 java.lang 包里面的类             result = jdkClassLoader.loadClass(name);         } catch (Exception e) {             //忽略         }         if (result != null) {             return result;         }         String classPath = classPathMap.get(name);         File file = new File(classPath);         if (!file.exists()) {             throw new ClassNotFoundException();         }          byte[] classBytes = getClassData(file);         if (classBytes == null || classBytes.length == 0) {             throw new ClassNotFoundException();         }         return defineClass(classBytes, 0, classBytes.length);     }      private byte[] getClassData(File file) { //省略 }  }

这里注意一点,我们重写了 loadClass 方法也就是意味着所有类包括 java.lang 包里面的类都会通过 MyClassLoaderCustom  进行加载,但类隔离的目标不包括这部分 JDK 自带的类,所以我们用 ExtClassLoader 来加载 JDK 的类,相关的代码就是:result =  jdkClassLoader.loadClass(name);

测试代码如下:

public class MyTest {      public static void main(String[] args) throws Exception {         //这里取AppClassLoader的父加载器也就是ExtClassLoader作为MyClassLoaderCustom的jdkClassLoader         MyClassLoaderCustom myClassLoaderCustom = new MyClassLoaderCustom(Thread.currentThread().getContextClassLoader().getParent());         Class testAClass = myClassLoaderCustom.loadClass("com.java.loader.TestA");         Method mainMethod = testAClass.getDeclaredMethod("main", String[].class);         mainMethod.invoke(null, new Object[]{args});     } }

执行结果如下:

TestA: com.java.loader.MyClassLoaderCustom@1d44bcfa TestB: com.java.loader.MyClassLoaderCustom@1d44bcfa

可以看到,通过重写了 loadClass 方法,我们成功的让 TestB 也使用MyClassLoaderCustom 加载到了 JVM 中。

三 总结

类隔离技术是为了解决依赖冲突而诞生的,它通过自定义类加载器破坏双亲委派机制,然后利用类加载传导规则实现了不同模块的类隔离。

关于Java中怎么实现类隔离加载问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网精选频道了解更多相关知识。

--结束END--

本文标题: Java中怎么实现类隔离加载

本文链接: https://lsjlt.com/news/281446.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java中怎么实现类隔离加载
    Java中怎么实现类隔离加载,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一 什么是类隔离技术只要你 Java 代码写的足够多,就一定会出现这种情况:系统新引入了一个中间件...
    99+
    2023-06-15
  • Java怎么自定义类加载器实现类隔离
    这篇“Java怎么自定义类加载器实现类隔离”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java怎么自定义类加载器实现类隔离...
    99+
    2023-07-05
  • Java自定义类加载器实现类隔离详解
    目录一、背景二、解决思路2.1 创建依赖所在目录2.2 定义操作接口2.3 编写并构建业务包2.4 自定义类加载器2.5 主流程步骤三、总结一、背景 某服务需要连接操作多种组件(每种...
    99+
    2023-03-02
    Java类加载器实现类隔离 Java类加载器 类隔离 Java类加载器 Java 类隔离
  • java自定义类加载器如何实现类隔离
    目录自定义类加载器准备通过URLClassLoader来实现【推荐】通过继承ClassLoader实现网上java自定义类加载器很多容易找到,但是都是加载的单个类,如果被加载的类,有...
    99+
    2022-11-21
    java类隔离 自定义类加载器 java类加载器
  • Java通过自定义类加载器实现类隔离
    目录前言类隔离是什么使用场景解决方案重写findClass重写loadClass总结前言 由于微服务的快速迭代、持续集成等特性,越来越多的团队更倾向于它。但是也体现出了一些问题,比如...
    99+
    2022-11-13
    Java 类加载器 类隔离 Java 类加载器 Java 类隔离
  • docker怎么实现资源隔离的
    Docker可以通过以下几种方式来实现资源隔离: 容器化技术:Docker使用Linux容器(LXC)技术来隔离容器之间的资源。...
    99+
    2023-10-26
    docker
  • 怎么在Java中实现懒加载
    这篇文章主要介绍“怎么在Java中实现懒加载”,在日常操作中,相信很多人在怎么在Java中实现懒加载问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么在Java中实现懒加载”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-07-05
  • C#接口隔离原则怎么实现
    今天小编给大家分享一下C#接口隔离原则怎么实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。接口隔离原则(ISP)定义:使用...
    99+
    2023-06-29
  • 怎么在mysql中实现事务的隔离级别
    这篇文章给大家介绍怎么在mysql中实现事务的隔离级别,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。事务就是一组一起成功或一起失败的sql语句。事务还应该具备,原子性,一致性,隔离性和...
    99+
    2024-04-02
  • 怎么在java中使用ThreadLocal隔离线程
    这期内容当中小编将会给大家带来有关怎么在java中使用ThreadLocal隔离线程,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java的特点有哪些Java的特点有哪些1.Java语言作为静态面向对象编...
    99+
    2023-06-14
  • springboot动态加载类怎么实现
    要实现Spring Boot动态加载类,可以使用以下步骤: 创建一个ClassLoader对象,用于动态加载类。可以使用URLC...
    99+
    2023-10-25
    springboot
  • jvm类加载机制怎么实现
    JVM类加载机制是通过类加载器来实现的。类加载器负责在JVM运行时将字节码文件加载到内存中,并将其转换为可执行的类。类加载器主要分为...
    99+
    2024-04-02
  • java热加载怎么实现的
    Java的热加载可以通过使用Java的反射机制和自定义的类加载器来实现。下面是一种常见的实现方式: 创建一个自定义的类加载器,继...
    99+
    2023-10-25
    java
  • java 类是怎么加载的
    类加载,就是读取 .class 文件到内存中,放入方法区,并在堆区创建一个 java.lang.Class 类型的对象,这个对象封装了类在方法区内的数据结构。类加载发生在何时?一般在类被使用时发生,包括下面 6 种情况:– 使用 new 关...
    99+
    2019-06-01
    java教程 java 加载
  • java中无法加载主类怎么办
    如果在Java中无法加载主类,可以尝试以下几种方法进行排查和解决:1. 检查是否正确设置了类路径(classpath)。确保将所有需...
    99+
    2023-09-07
    java
  • SQL Server中怎么实现事务操作隔离模式
    这期内容当中小编将会给大家带来有关SQL Server中怎么实现事务操作隔离模式,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。  1.脏数据读写,这种情况发生在当一个事务...
    99+
    2024-04-02
  • C#中怎么实现并使用接口隔离原则
    在C#中实现接口隔离原则,可以通过定义多个小而精简的接口,而不是一个大而臃肿的接口。这样可以让每个接口只包含一个特定功能或行为,使得...
    99+
    2024-04-03
    C#
  • java热加载是怎么实现的
    Java热加载是通过类加载器(ClassLoader)实现的。在Java应用程序运行时,类加载器负责加载类文件并将其转换为字节码。当...
    99+
    2024-04-02
  • 怎么解析Java类加载器
    这篇文章给大家介绍怎么解析Java类加载器,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Jav...
    99+
    2023-06-02
  • Java中接口隔离原则是什么
    小编给大家分享一下Java中接口隔离原则是什么,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1.什么是接口隔离原则?客户端不应该依赖它不需要的接口,即一个类对另一...
    99+
    2023-06-29
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作