前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >动态加载子类热更

动态加载子类热更

作者头像
Qwe7
发布2022-03-23 08:11:57
4200
发布2022-03-23 08:11:57
举报
文章被收录于专栏:网络收集

正文

上一篇《JAVA热更新1:Agent方式热更》我们讲解了JDK提供的Agent方式来实现代码不停服更新, 受限于JDK的Agent一些限制,这种方式无法实现以下功能:只能修改方法体,不能变更方法签名、不能增加和删除方法/类的成员属性。

对于Instrumentation和JVM的agent,网上有不少文章,大家可以自行参考,今天我们来了解下第二种热更方式:动态加载子类热更

核心思路

热更新,顾名思义就是要替换代码实现。根本需求就是我怎么把改好的class替换进JVM中并实现逻辑调用。

一种办法就是直接替换代码逻辑,上一篇JVM提供的agent方式就属于此方法。那如果我们想从代码层级上实现代码替换有什么方式呢? 设计模式中有一种模式:代理模式,它的原理是对原类生成一个代理类并注册到系统中,应用层使用的是代理类,从而在代理层可以增加许多逻辑,Spring框架就是典型的应用者。

我们这里可以参考代理的思路,采用子类的方式来进行代码实现的逻辑替换。

如上图,Parent类中有一个方法method1(),如果我们想改变里面逻辑,可以写一个Parent的子类SubClass,然后覆写方法method1(), 这样外部调用还是Parent,但实际调用的对象替换为SubClass,即可实现代码的替换。

几个细节点

目前我们有了大概的思路,具体实现还有以下几个细节需要考虑:

  • 如何生成子类?
  • 生成的类如何加载进入jvm?
  • 代码中如何调用才能实现调用的替换?

如何生成子类?

我们期望的热更方式是把修改后的class上传到原路径下并覆盖,那应该如何动态生成子类呢?

关于动态生成类的开源框架有几种:asm、cglib、javaassit,各有利弊。 这里应用场景是热更新,所以对性能要求不高,但考虑到可读性和维护性,项目中尽量也不考虑直接操作字节码, 所以最终我们选择了javaassist框架,它是可以直接通过java代码来构建新类。

具体做法:

从原路径上读取修改后的class文件的二进制字节流,并通过javaassist框架构建新的class,对新class进行如下操作:

  • 改名,新类名为:原名+$$$SUBCLASS
  • 让新类继承原类
  • 设置子类的构造函数为public,且调用父类的默认构造函数,方便后续反射构建对象
  • 忽略父类里的final方法,因为final是无法继承的,覆写会导致语法报错

生成的类如何加载进入jvm?

class想要加载进入jvm,唯一途径就是通过ClassLoader,因此这里我们自实现RecompileClassLoader继承于ClassLoader,实现二进制字节加载class进入JVM

对象注册机制?

现在我们已经有了一个新子类,它继承于原类,且覆写了原类的方法,那业务层怎么才能不修改代码的情况下能自动实现SubClass替换Parent实现逻辑?

解决方案就是对象注册机制,简单理解就是对象的映射关系。 我们应用层用的都是从注册机制获取的,这样进行热更时,我们只要把当前注册的对象替换为新对象,因为新对象是原对象的子类,可覆写方法,从而实现逻辑的替换。

具体类图如下:

如何不停服新增功能?

通过上面流程,我们知道本方法原理就是:读取一个class文件,并动态加载进入jvm虚拟机,从而实现代码替换。

那基于上面的注册机制,那附带就有了一个新功能:动态新增注册类(也就是RegistryManager.registerNewOne())。 比如新写一个注册类,调用注册系统接口可编译新类并注册进系统中, 尤其对于游戏服务,一般的逻辑都是走消息号映射逻辑的,天然适合注册机制,这样线上可动态新增消息号和对应的实现逻辑,从而达到不停服增加功能的目的。

优缺点对比

两者:都支持对特定逻辑进行热更

热更类型

优点

缺点

Agent方式

对于JVM的类基本都可以热更

只能修改方法体,不能变更方法签名、不能增加和删除方法/类的成员属性。某些特定情况下,有极低机率导致JVM崩溃(可能是JVM的BUG,暂无法复现)

动态编译新类

因为采用的是新生成,所以支持修改签名,新增方法甚至新增实现等

需要把热更的逻辑按照注册机制编写,否则无法热更

总结

最终我们形成了这样的流程:

  • 本地修改bug,并把修改后的class上传服务器
  • 热更时,读取修改后的class文件,按照一定流程对原class进行重新构建,生成子类
  • 将子类注册到注册系统,从而实现子类的替换
  • 业务层基于注册系统获取的对象是原来子类,则实际调用的API是子类的实现
  • 基于此,还可以动态注册新的逻辑到系统中,实现不停服新增功能

示例代码github:

https://github.com/cm4j/cm4j-all

运行测试

运行 RegistryManagerTest.hotswapTest(),结果如下:

代码语言:javascript
复制
 [main] ERROR com.cm4j.registry.RegistryManagerTest - 热更新前的类:class com.cm4j.demo.util.DemoUtil[main] ERROR com.cm4j.hotswap.recompile.RecompileHotSwap - class dumpd: D:\Projects\others\cm4j-projects\cm4j-all\cm4j-hotswap\recompile-output\DemoUtil$$$SUBCLASS-20211111161652.class[main] ERROR com.cm4j.registry.AbstractRegistry - [hotswap] success:class com.cm4j.demo.util.DemoUtil -> com.cm4j.demo.util.DemoUtil$$$SUBCLASS@148080bb[main] ERROR com.cm4j.registry.RegistryManagerTest - 热更新后的类(已替换为原类的子类):class com.cm4j.demo.util.DemoUtil$$$SUBCLASS

由此可见,经过热更之后,业务调用类已由DemoUtil替换为DemoUtil$$$SUBCLASS,且两者是父子关系。可有效解决外部基于对象类型判断的问题

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正文
  • 核心思路
  • 几个细节点
    • 如何生成子类?
      • 生成的类如何加载进入jvm?
        • 对象注册机制?
          • 如何不停服新增功能?
          • 优缺点对比
          • 总结
          • 示例代码github:
          • 运行测试
          相关产品与服务
          云服务器
          云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档