文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java通过自定义类加载器实现类隔离

2022-11-13 14:31

关注

前言

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

类隔离是什么

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

使用场景

比如业务服务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

 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");
  }
  
  
  @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[]{};
  }

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

重写loadClass

 private ClassLoader classLoader;
 
 
 @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类加载相关的知识点,也算是一劳多得,后续会结合实际使用场景进一步分析。

以上就是Java通过自定义类加载器实现类隔离的详细内容,更多关于Java类隔离的资料请关注编程网其它相关文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯