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

动态加载子类热更

作者头像
Qwe7
发布于 2022-03-23 00:11:57
发布于 2022-03-23 00:11:57
43400
代码可运行
举报
文章被收录于专栏:网络收集网络收集
运行总次数:0
代码可运行

正文

上一篇《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
代码运行次数:0
运行
AI代码解释
复制
 [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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
JAVA热更新1:Agent方式热更
线上问题的解决一直是java程序员头疼的一个问题。有的应用很敏感,例如游戏行业,可能需要做到1个月都不能停服,那线上问题出了问题怎么办呢?谁也不能保证更新线上的逻辑百分百不出问题,这就需要我们在不停应用的情况下在线解决。随着技术的逐渐成熟,java社区也逐渐提供了一些线上解决方案,比如说下面3个方面:
Qwe7
2022/03/23
3.2K0
热加载原理解析与实现
热加载可以在修改完代码后,不重启应用,实现类信息更新,以节省开发时等待启动时间。本文主要从热加载概念、原理、常见框架、实现等角度为你揭开热加载的层层面纱。
星沉
2022/06/19
5.8K0
游戏服务器线上出bug,怎么办?急,在线等!
一直在写基础篇,本来想按顺序来,但是想想无所谓了,只要是计划内的就好,今天聊一下热更新。
香菜聊游戏
2021/05/26
6310
游戏服务器线上出bug,怎么办?急,在线等!
Instant run动态加载机制
关于动态加载,实际上Instant run提供了两种动态加载的机制: 1.修改java代码需要重启应用加载补丁dex,而在Application初始化时替换了Application,新建了一个自定义的ClassLoader去加载所有的dex文件。我们称为重启更新机制 2.修改代码不需要重启,新建一个ClassLoader去加载修改部分。我们称为热更新机制
老马的编程之旅
2022/06/22
7150
一文搞定Java热更新
在持续交付的时代,重新部署一个新的版本只需要点击一下按钮。但在有的情况下,重新部署过程可能比较复杂,停机是不被允许的。所以JVM提供了另外一种选择:在不重启应用的前提下进行小幅改动,又称热更新。
全菜工程师小辉
2019/09/26
3.6K0
一文搞定Java热更新
从Java的类加载机制谈起:聊聊Java中如何实现热部署(热加载)
ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(…)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(…)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。
程序员黄小斜
2021/12/08
3.4K0
谁还没遇上过NoClassDefFoundError咋地——浅谈字节码生成与热部署
谁还没遇上过NoClassDefFoundError咋地——浅谈字节码生成与热部署 ---- 前言 在Java程序员的世界里,NoClassDefFoundError是一类相当令人厌恶的错误,因为这类错误通常非常隐蔽,难以调试。 通常,NoClassDefFoundError被认为是运行时类加载器无法在classpath下找不到需要的类,而该类在编译时是存在的,这就通常预示着一些很麻烦的情况,例如: 不同版本的包冲突。这是最最最常见的情况,尤其常见于用户代码需要运行于容器中,而本地容器和线上容器版本不同时
老白
2018/07/06
1K0
Java热更新
最近参与开发一个java项目,每次修改调试时就需要重启进程,由于工程较大,进程初始化任务较多,重启较慢,严重影响了开发效率,因此花了点时间研究java热更新机制,在项目中引入热更新后,每次的修改可以立即看到结果,提高了开发效率。
jemuelmiao
2019/12/03
1.8K0
谈谈Java Intrumentation和相关应用
  对于Java 程序员来说,Java Intrumentation、Java agent这些技术可能平时接触的很少,听上去陌生但又好像在哪里见到过。实际上,我们日常应用的各种工具中,有很多都是基于他们实现的,例如常见的热部署(JRebel, spring-loaded)、各种线上诊断工具(btrace, Greys)、代码覆盖率工具(JaCoCo)等等。   本文会介绍 Java Instrumentation及其相关概念,会涉及到的名词包括:
JavaEdge
2020/05/27
1.1K0
谈谈Java Intrumentation和相关应用
手把手教你实现热更新功能,带你了解 Arthas 热更新背后的原理
一天下午正在摸鱼的时候,测试小姐姐走了过来求助,说是需要改动测试环境 mock 应用。但是这个应用一时半会又找不到源代码存在何处。但是测试小姐姐的活还是一定要帮,突然想起了 Arthas 可以热更新应用代码,按照网上的步骤,反编译应用代码,加上需要改动的逻辑,最后热更新成功。对此,测试小姐姐很满意,并表示下次会少提 Bug。
andyxh
2019/11/18
1.9K0
手把手教你实现热更新功能,带你了解 Arthas 热更新背后的原理
Java系列 | 远程热部署在美团的落地实践
总第495篇 2022年 第012篇 Sonic是美团内部一款用于热部署的IDEA插件。本文主要讲述Sonic的实现细节以及底层原理,从IDEA插件到自动化部署,再到沉浸式开发产品闭环,全方位讲述了Sonic在美团的落地与实践经验。目前业界对标的产品并不多,希望本文能对从事联调/开发/测试等相关方向的同学有所帮助或启发。 1 前言 1.1 什么是热部署 1.2 为什么我们需要热部署 1.3 热部署难在哪 1.4 Sonic可以做什么 1.5 技术产品落地和推广实践经验 2 整体设计方案 2.1 Sonic
美团技术团队
2022/03/18
2K0
skywalking源码分析之javaAgent工具ByteBuddy的应用
关于skywalking请看我上一篇博文,其使用javaAgent技术,使得应用接入监控0耦合。今天在分析skywaking过程中,对javaAgent技术有了更深入的了解。skywalking使用的javaAgent工具ByteBuddy是一个比ASM更上层的针对java字节码操作的封装,基于ByteBuddy,我们可以快速方便的对java字节码进行增强处理,更高效的开发javaAgent应用。
kl博主
2018/04/13
3.3K0
skywalking源码分析之javaAgent工具ByteBuddy的应用
动态代理竟然如此简单!
动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理、RPC远程调用、Java 注解对象获取、日志框架、全局性异常处理、事务处理等。
cxuan
2021/01/06
3700
动态代理竟然如此简单!
jAVA不停服执行代码
尽管我们有了JAVA热更新1:Agent方式热更、JAVA热更新2:动态加载子类热更,能修复大部分线上的BUG,在项目上线之后,不可避免的会遇到出数据错乱的情况。之前的做法可能是提前写好一段代码,然后通过后台接口来进行调用,用以解决线上数据规整。但这种方式必须得提前写好规整逻辑,但不能覆盖所有情况。
Qwe7
2022/03/23
5190
如何编写高质量的代码
Java开发中通用的方法和准则不要在常量和变量中出现易混淆的字母枚举类中不要提供setter三元操作符的类型务必一致避免带有变长参数的方法重载少用静态导入避免为final变量复杂赋值break万万不可忘避免instanceof非预期结果基本类型不要让四舍五入亏了一方提防包装类型的null值谨慎包装类型的大小比较类、对象及方法在接口中不要存在实现代码构造函数尽量简化使用静态内部类提高封装性让工具类不可实例化覆写equals方法时不要识别不出自己推荐覆写toString方法使用package-info类为包服务数组和集合在明确的场景下,为集合指定初始容量避开基本类型数组转换列表陷阱asList方法产生的List对象不可更改子列表只是原列表的一个视图推荐使用subList处理局部列表生成子列表后不要再操作原列表使用Comparator进行排序不推荐使用binarySearch对列表进行检索;集合中的元素必须做到compareTo和equals同步;使用shuffle打乱列表;减少HashMap中元素的数量;多线程使用Vector或HashTable;非稳定排序推荐使用List枚举和注解推荐使用枚举定义常量;使用构造函数协助描述枚举项;小心switch带来的空值异常;在switch的default代码块中增加AssertionError错误;使用valueOf前必须进行校验;枚举项的数量控制在64个以内;小心注解继承;枚举和注解结合使用威力更大;注意@Override不同版本的区别;泛型和反射强制声明泛型的实际类型;不同的场景使用不同的泛型通配符;严格限定泛型类型采用多重界限;注意Class类的特殊性;适时选择getDeclaredXXX和getXXX;反射访问属性或方法是将Accessible设置为true;使用forName动态加载类文件;动态代理可以使代理模式更加灵活;使用反射增加装饰模式的普适性;反射让模板方法模式更强大;不需要太多关注反射效率;异常提倡异常封装;采用异常链传递异常;受检异常尽可能转化为非受检异常;不要在finally块中处理返回值;多使用异常,把性能问题放一边;多线程和并发不推荐覆写start方法;启动线程前stop方法是不可靠的;不适用stop方法停止线程;线程优先级只使用三个等级;使用线程异常处理器提升系统可靠性;volatile不能保证数据同步;异步运算考虑使用Callable接口;优先选择线程池;适时选择不同的线程池来实现;Lock与synchronized是不一样的;预防线程死锁;适当设置阻塞队列长度;使用CountDownLatch协调子线程;CyclicBarrier让多线程齐步走;开源世界大胆采用开源工具;推荐使用Guava扩展工具包;Apache扩展包;推荐使用Joda日期时间扩展包;可以选择多种Collections扩展;思想为源提倡良好的代码风格;不要完全依靠单元测试来发现问题;让注释正确、清晰、简洁;让接口的职责保持单一;增强类的可替换性;依赖抽象而不是实现;抛弃7条不良的编码习惯;以技术人员自律而不是工人
双鬼带单
2020/07/25
1K0
手把手教你实现一个方法耗时统计的 java agent
上面两节虽然手把手教你实现了一个 hello world 版 agent,然而实际上对 java agent 依然是一脸茫然,所以我们得先补齐一下基础知识
一灰灰blog
2020/03/20
1.1K0
手把手教你实现一个方法耗时统计的 java agent
初识AOP与动态代理
AOP AOP是指在jvm运行时, 动态将代码切入到指定位置. OOP是一个维度上写代码, AOP是把他切开来, 变成立体的. 这样的好处是: 业务逻辑跟辅助逻辑分离, 例如日志打印, 性能监控, 安
用户1216491
2018/01/24
4910
如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈
很久没见,甚是想念,大家好啊!很久没写技术文章了,手也会痒呢。本文干货含量高,建议先收藏再看哦!
Rude3Knife的公众号
2022/12/10
1.6K0
如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈
一种Android App在Native层动态加载so库的方案
这篇文章通过实战案例,介绍了一种有条理的组织Native层代码层级结构的方法。并且,在良好的代码层级、作用分工的基础上,实现了动态的按需加载、卸载so库。文章的最后,还介绍了实践过程中遇到的困难以及对应的解决方案,能让读者少走弯路。 — 责任编辑 wingyipye 1. 为什么在Native层动态加载so库 随着Android App发展的不断变化,App的性能和系统API框架外的功能拓展显得越来越重要。App从性能方面考虑,需要在Native层使用C/C++实现的方案,Native层再通过JNI的方
QQ音乐技术团队
2018/02/01
7.5K1
一种Android App在Native层动态加载so库的方案
实现一个javaagent需要几步?
在介绍javaagent之前,我想有必要向大家介绍一下JVMTI,因为javaagent是基于这个技术实现的
tnt阿信
2021/11/11
7830
实现一个javaagent需要几步?
推荐阅读
相关推荐
JAVA热更新1:Agent方式热更
更多 >
LV.7
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验