0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

类隔离的使用场景

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-08 15:20 次阅读

前言

由于微服务的快速迭代、持续集成等特性,越来越多的团队更倾向于它。但是也体现出了一些问题,比如在基础设施建设过程中,需要把通用功能下沉,把现有大而全的基础设施按领域拆分,考虑需要兼容现有生产服务,会产生不同的依赖版本,有时不注意就可以引发问题。比如本文遇到的依赖包版本冲突问题,以及如何利用类隔离技术解决的分析。

类隔离是什么?

类隔离是一种通过类加载器实现加载所需类的实现方式,使得不同版本类间隔离,避免了使用冲突问题,最终的效果就是不同模块的内容被不同的类加载器加载,满足同一环境下同时兼容不同接口实现类。

使用场景

比如业务服务A和业务服务B均需要消息通知等,均依赖消息中间件,但所引用版本不一致,导致最终只有一个版本加载到JVM,在某一个服务调用时会出现 NoSuchMethodError或NoSuchClassError问题,这就很难排查出来,没准会影响项目进度,最终月度的绩效(“鸡腿”)不保。

服务A pom.xml:

< !-- common-message-- >
        < dependency >
            < groupId >com.lgy< /groupId >
            < artifactId >spring-common-message< /artifactId >
            < version >1.0.0< version >
        < /dependency >

服务B pom.xml:

< !-- common-message-- >
        < dependency >
            < groupId >com.lgy< /groupId >
            < artifactId >spring-common-message< /artifactId >
            < version >2.0.0< version >
        < /dependency >

业务调用流程:

// 业务A调用微信服务通知
 MessageUtil.sendMessage(content,peopleId,templateId,"wechat");
 // 业务B调用微信服务通知
 MessageUtil.sendToWechat(content,peopleId,templateId);

JVM最终加载的为 2.0.0 版本的依赖,导致业务A在调用时抛异常java.lang.NoSuchMethodError。

解决方案

大体的解决思路就是,在不改变业务代码的前提下, 业务A调用 1.0.0 版本的消息工具类, 业务B调用2.0.0版本的消息工具类,因此需要JVM能够利用自定义类加载器加载所需的类或关联的类。

实现思路

  • 重写类加载器,实现自定义类加载(java.lang.ClassLoader)
  • 重写类加载函数
    • 重写 findClass(String name)
    • 重写 loadClass(String name)

涉及的知识点

  • JVM加载过程:加载-》链接-》初始化(具体后续介绍)
  • 双亲委派机制:委托父加载器查询;如果父加载器查询不到,则调用自身的findClass加载

重写findClass:

import java.io.*;
 import java.util.HashMap;
 import java.util.Map;

 public class CustomerFindClass extends ClassLoader {
  private Map< String, String > classPathMap = new HashMap<  >();
  public CustomerFindClass() {
   // 业务A的自定义类加载器
   classPathMap.put("com.lgy.businessA.service.impl.MessageServiceImpl", "E:/dataway-demo/example/target/classes/com/lgy/businessA/service/impl/MessageServiceImpl.class");
   classPathMap.put("com.lgy.v1.message.util.MessageUtil", "E:/dataway-demo/example/target/classes/com/lgy/v1/message/util/MessageUtil.class");
  }
  
  /**
  * findClass方式加载类
  */
  @Override
  protected Class< ? > findClass(String name) throws ClassNotFoundException {
   String classPath = classPathMap.get(name);
   File file = new File(classPath);
   if (!file.exists()) {
    throw new ClassNotFoundException();
   }
   byte[] bytes = getClassData(file);
   if (null == bytes || 0 == bytes.length) {
    throw new ClassNotFoundException();
   }
   return defineClass(bytes, 0, bytes.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[]{};
  }

最终结果与预期的结果不一致

  • 预期结果:业务A的MessageServiceImpl与MessageUtil由CustomerFindClass加载
  • 实际结果:业务A的MessageServiceImpl由CustomerFindClass加载,而MessageUtil由sun.misc.AppClassLoader加载。
  • 分析:由于JVM类加载的双亲委托机制,业务A调用消息工具类时,类加载器(CustomerFindClass)会委托父类加载器(AppClassLoader)加载类,如果存在,则不再执行自身的findClass方法加载,导致结果不理想。(main 方法类默认情况下都是由 JDK 自带的 AppClassLoader 加载的)。

重写loadClass

private ClassLoader classLoader;
 
 /**
 * 重新loadClass方法
 */
 @Override
    protected Class< ? > loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class result = null;
        try {
            //这里要使用 JDK 的类加载器加载 java.lang 包里面的类
            result = classLoader.loadClass(name);
        } catch (Exception e) {
            // ignore error
        }
        if (null != result) {
            return result;
        }
        String classPath = classPathMap.get(name);
        File file = new File(classPath);
        if (!file.exists()) {
            throw new ClassNotFoundException();
        }
        byte[] bytes = getClassData(file);
        if (null == bytes || 0 == bytes.length) {
            throw new ClassNotFoundException();
        }
        return defineClass(bytes, 0, bytes.length);
    }

满足业务A的MessageServiceImpl与MessageUtil由CustomerFindClass加载

注意:这种方式破坏了双亲委托机制,但由于重写了loadClass方法,所有类均会有CustomerFindClass加载器加载,需要过滤出不需要隔离的类,如java.lang包下的类,需要由ExtClassLoader 来加载。

总结

本文分享的方式是从类加载器方向出发,实现最终的类隔离,避免了不同模块间不同类的冲突,其中顺便也简单带过了jvm类加载相关的知识点,也算是一劳多得,后续会结合实际使用场景进一步分析。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7967

    浏览量

    149234
  • 函数
    +关注

    关注

    3

    文章

    4119

    浏览量

    61550
  • 微服务
    +关注

    关注

    0

    文章

    116

    浏览量

    7260
  • 类加载器
    +关注

    关注

    0

    文章

    6

    浏览量

    907
收藏 人收藏

    评论

    相关推荐

    AG32VF-MIPI应用场景

    MIPI接口技术在图像和视频传输中的应用越来越广泛,应用场景也在不断拓展,而不仅限于移动设备。MIPI接口在物联网、智能家居、智能监控、智能电视、智能汽车等领域也得到广泛应用。 MIPI还可
    发表于 01-22 08:56

    可展示三种RS485应用场景的半双工参考设计包括BOM及层图

    描述 RS485(隔离式、非隔离式)是适用于电网基础设施空间的常用接口,也是新设计的设备上最重要的选项之一。TIDA-00308 允许客户针对三种不同应用场景通过 TI RS485 器件以及该
    发表于 09-21 09:15

    MOS管的应用场景

    mos管的应用场景,你了解么?低压MOS管可称为金属氧化物半导体场效应管,因为低压MOS管具有良好的开关特性,广泛应用在电子开关的电路中。如开关电源,电动马达、照明调光等!下面银联宝科技就跟大家一起
    发表于 11-14 09:24

    this的使用场景及与C,Java中的this的区别

    【JS】this有哪些使用场景?跟C,Java中的this有什么区别?如何改变this的值?
    发表于 03-11 10:17

    CP-OFMD调制波形应用场景

    图1、5G的应用场景5G使用5G多载波波形来为智能手机,办公室,工厂自动化,智能电网,智慧城市,物联网,M2M,M2X等多种设备提供应用平台。5G新无线电(5G NR)根据应用场景可分为三大服务
    发表于 06-18 06:51

    =>的使用场景有哪些

    使用场景
    发表于 10-27 13:25

    运放电路有哪些应用场景?

    运放电路的七大应用场景
    发表于 03-11 07:49

    蓝牙低功耗常见的应用场景及架构

    浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构
    发表于 06-15 09:51

    FPGA的应用场景

    目录文章目录目录FPGAFPGA 的应用场景FPGA 的技术难点FPGA 的工作原理FPGA 的体系结构FPGA 的开发FPGA 的使用FPGA 的优缺点参考文档FPGAFPGA(Field
    发表于 07-28 08:43

    ARM的技术特征是什么?应用场景有哪些?

    ARM的技术特征是什么?应用场景有哪些?
    发表于 11-05 07:32

    MS9331的应用场景是什么?

    MS9331的应用场景是什么?
    发表于 02-11 06:41

    RK3308的特点及应用场景是什么?

    RK3308的特点及应用场景是什么?
    发表于 03-09 08:04

    labview 和 wincc 的区别 使用场景

    labview 和 wincc 的区别 使用场景 都是上位机软件,都可以做监控软件 wincc的名气也比较大 对比的资料较少 写这些文章的人,从自己的从事的行业出发,带有自己的思维 使用的场景 肯定
    发表于 10-27 18:01

    荣湃隔离驱动器的应用场景有哪些?

    隔离驱动器的一大应用场景是取代数字隔离器+MOS驱动器的分立设计,高绝缘耐压要求的半桥驱动应用中,部分系统采用了双通道数字隔离器配合高低边MOS驱动器的设计。
    的头像 发表于 04-01 15:42 1876次阅读

    光耦怎么用?光耦的输入和输出到底是什么关系?

    LED的亮灭;输出端是光敏电阻,通过测量光敏电阻的电阻值来获取输出信号。 光耦常用于电气隔离和信号隔离的应用场景中。当系统中存在不同电压级别、不同电源地等情况时,为了避免干扰和保护电路,可以使用光耦来实现输入与输出的
    的头像 发表于 02-03 17:06 1563次阅读