Spring Loaded代码热更新实践和原理分析
1、引言
开发者在编码效率和快速迭代中的痛点场景包括:
修改代码后,需要频繁重启应用,导致开发效率低下;
实时调试时,不能立即看到代码修改的结果;
大型项目中,重启的时间成本较高。
针对这些问题,本文将深入探讨如何利用Spring Loaded热更新技术提高开发效率,减少编译和重启时间。分析Spring Loaded的热更新原理,以及实际应用过程中所需的操作和注意事项。
2、框架简介
Spring Loaded is a JVM agent for reloading class file changes whilst a JVM is running. It transforms classes at loadtime to make them amenable to later reloading. Unlike 'hot code replace' which only allows simple changes once a JVM is running (e.g. changes to method bodies), Spring Loaded allows you to add/modify/delete methods/fields/constructors. The annotations on types/methods/fields/constructors can also be modified and it is possible to add/remove/change values in enum types.
Spring Loaded 是一个 JVM 代理,可以在 JVM 运行时重新加载类文件的更改。它会在加载时转换类,以便稍后重新加载。与“热代码替换”只允许在 JVM 运行时进行简单更改(例如更改方法体)不同,Spring Loaded 允许您添加/修改/删除方法/字段/构造函数。还可以修改类型/方法/字段/构造函数上的注解,并且可以添加/删除/更改枚举类型中的值。
3、如何使用
3.1 下载Agent插件
https://repo1.maven.org/maven2/org/springframework/springloaded/1.2.8.RELEASE/springloaded-1.2.8.RELEASE.jar
3.2 引入Agent插件
在jvm的启动命令中添加以下参数
-javaagent:/Users/you/runtime/springloaded-1.2.8.RELEASE.jar -noverify
3.3 修改并重新编译
修改代码后执行Build->Recompile命令,可以看到在class reloaded完成后,程序的运行逻辑发生了变化
4、原理分析
4.1 代码编译分析
先来看一段源代码,这是一个RpcService类,定义了target字段、targetStatic静态字段和say方法,现在我们编译它。
public class RpcService {
private String target = "rpc";
private static String targetStatic = "rpc static";
public String say() {
return "RpcService say hello SpringLoaded" + target;
}
}
SpringLoaded对类编译后添加了一些跟踪记录字段,添加方法拦截判断。
public static ReloadableType r$type = TypeRegistry.getReloadableType(0, 1);
public transient ISMgr r$fields;
public static final SSMgr r$sfields;
public String hello() {
if (r$type.changed(0) == 1) {
return r$type.fetchLatest()).say(this);
}
String targetNew = TypeRegistry.instanceFieldInterceptionRequired(1, "target") ? (String)r$get(this, "target") : this.target;
return "RpcService say hello SpringLoaded" + targetNew;
}
我们可以在代码运行时,使用getDeclaredField、getDeclaredMethod等函数在运行时获取类成员、方法信息,此时可以看到增强后的类多了如下字段和方法。
在编译后的代码中,我们可以看到RpcService类包含了一些新的字段和方法,这些都是Spring Loaded框架增加的。
•r$type是一个静态变量,其类型为ReloadableType。这个字段用于表示当前类的可重载类型,它包含了当前类的最新字节码和其他相关信息。
- r\(get、r\)set方法是用于获取实例字段的值的方法,处理字段的拦截和替换。
- ___clinit___方法是用于执行类的静态初始化块的方法。
- init()方法是用于处理类的构造函数的方法。
- 在say()方法中增加了一个代码片段用于判断类是否发生了变更,如果变更了,则调用最新的可重载类型中的say()方法获取结果。否则,继续执行原有的方法体。在方法体中,也增加了一个代码片段用于判断本地变量是否需要拦截,如果需要,则使用r$get()方法获取非静态变量traget的值,并用它替换原有的变量值。
4.2 运行过程分析
1、在应用程序启动时,Spring Loaded在目标类路径中查找所有的类,并在ClassPreProcessor中使用自定义类加载器加载这些类,重新定义后存入TypeRegistry,用于缓存、变更对比和依赖关系维护。
2、注册一个文件变化监听器FileChangeListener,当一个类文件被修改后,Spring Loaded会检测到这个变化,并重新加载该类文件。
3、当一个类被重新加载时,Spring Loaded会尝试对比类的签名和继承关系没有改变,如果新的类定义与之前的类定义兼容,那么Spring Loaded会更新应用程序中的对象引用,以指向新的类定义。
5、总结
Spring-loaded 使用 Java 的 Instrumentation API 在 JVM 启动时指定 Agent,使它能够在目标类加载之前进行拦截,并将目标类的字节码通过 ASM 库解析成抽象语法树(AST),然后对 AST 进行修改。修改的内容包括增加、删除、替换方法,修改方法体,添加字段等,最终替换目标类,改变其逻辑,实现对代码的热更新。
6、扩展内容
•Jrebel也可以实现类似热更新功能,并且它更高效、稳定。jrebel官网
•Spring-boot-devtools也可以提升开发速度,但是它的方案更像是热重启。Spring Boot Devtools Restarter 原理
•如何自己实现一个热更新功能呢?思路大同小异,实现各有千秋。如何自己实现一个热加载?如何定义自己的类加载器?
作者:京东零售 程啸
来源:京东云开发者社区
Spring Loaded代码热更新实践和原理分析的更多相关文章
- swoole之代码热更新实现 转自https://blog.csdn.net/nep_tune/article/details/81329918
随着swoole的版本迭代更新,已经足够稳定了,在阿里,腾讯,yy等各大公司都有着使用,也有很多游戏圈里的朋友也在使用,这些朋友经常会提到一个问题,每次代码更新还需要停止服务,然后重新启动,来达到更新 ...
- Unity3D热更新之LuaFramework篇[09]--资源热更新与代码热更新的具体实现
前言 在上一篇文章 Unity3D热更新之LuaFramework篇[08]--热更新原理及热更服务器搭建 中,我介绍了热更新的基本原理,并且着手搭建一台服务器. 本篇就做一个实战练习,真正的来实现热 ...
- Cordova 代码热更新 - 简书
原文:Cordova 代码热更新 - 简书 Cordova 代码热更新 [图片上传失败...(image-a19be7-1521624289049)] 基于 Cordova 框架能将网页应用 (js, ...
- React Native超简单完整示例-tabs、页面导航、热更新、用户行为分析
初学React Native,如果没有人指引,会发现好多东西无从下手,但当有人指引后,会发现其实很简单.这也是本人写这篇博客的主要原因,希望能帮到初学者. 本文不会介绍如何搭建开发环境,如果你还没有搭 ...
- 手把手教你实现热更新功能,带你了解 Arthas 热更新背后的原理
文章来源:https://studyidea.cn/java-hotswap 一.前言 一天下午正在摸鱼的时候,测试小姐姐走了过来求助,说是需要改动测试环境 mock 应用.但是这个应用一时半会又找不 ...
- Unity代码热更新方案 JSBinding + SharpKit 首页
目前Unity的代码更新方案有很多,主要以lua为主. JSBinding + SharpKit 是一种新的技术,他做了两件事情: JSBinding将C#导出到 JavaScript (引擎是 Mo ...
- SpringBoot魔法堂:应用热部署实践与原理浅析
前言 后端开发的同学想必每天都在重复经历着修改代码.执行代码编译,等待--重启Tomcat服务,等待--最后测试发现还是有bug,然后上述流程再来一遍(我听不见)
- ElasticSearch5.0+版本分词热更新实践记录
前言 刚开始接触ElasticSearch的时候,版本才是2.3.4,短短的时间,现在都更新到5.0+版本了.分词和head插件好像用法也不一样了,本博客记录如何配置Elasticsearch的Hea ...
- Spring Cloud之负载均衡组件Ribbon原理分析
目录 前言 一个问题引发的思考 Ribbon的简单使用 Ribbon 原理分析 @LoadBalanced 注解 @Qualifier注解 LoadBalancerAutoConfiguration ...
- nodeJs 代码热更新
在开发node过程中,每次修改代码都需要重新启动服务,是一件很抓狂的事情 使用nodemon热加载可以帮我们很好的解决这一问题 1. 安装 npm install nodemon -g 2. 修改np ...
随机推荐
- [Linux]RabbitMQ - 解决Error: unable to connect to node rabbit@localhost: nodedown
1 问题 环境: CentOS7.8.2003 (x86 / 64bit) 版本: RabbitMQ 3.6.15 (Erlang 19.3) 安装方式: 二进制源码压缩安装 2 解决思路 2.1 思 ...
- 五月二十五日jdbc基础知识点
Jdbc连接数据库1.建立与数据库的连接1.1导入jdbc包1.2加载JDBC驱动java.lang.Class.forName(JDBCDriverClass);Class.forName(driv ...
- DG:重启之后主备数据重新同步
问题描述:本来配置好的DG第二天重启之后,发现主备库数据不能同步,在主库上执行日志切换以及创建表操作都传不到备库上,造成这种错误的原因是主库实例断掉后造成备库日志与主库无法实时接收 主库:orcl ...
- SpringBoot应用集成微服务组件Nacos
目录 springboot与微服务组件nacos Nacos服务快速启动 STS4 开发工具 Maven 环境配置 STS4开发工具引入Maven配置 Maven Repo配置阿里云镜像源 Sprin ...
- vue路由的两种方式(路由传参)
query和params区别 query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在 params类似 po ...
- Python 变量作用域和列表
变量作用域 变量由作用范围限制 分类:按照作用域分类 全局(global):在函数外部定义 局部(local):在函数内部定义 变量的作用范围: 全局变量:在整个全局范围有效 全局碧昂量在局部可以使用 ...
- java镜子之反射篇
文章目录 注解 内置注解 元注解 反射 类的初始化 类加载器 双亲委派机制 反射方法的使用 调用类的方法.成员变量.构造器等 总结 注解和反射是Java中非常重要的知识,一些优秀开源的框架都是大量运用 ...
- 【解决方法】windos server 2019 在批量创建DNS的正向与反向记录时,提示报错: >Command failed: ERROR_ACCESS_DENIED 5 0x5
目录-快速跳转 问题描述 原因分析: 解决方案: 附言: 问题描述 操作环境与场景: 在 VM 内 windos server 2019 在批量创建DNS的正向与反向记录时,提示报错: Command ...
- Prism Sample 14-UsingEventAggregator
这次是事件聚合器的应用. 事件聚合器应用第一步:定义一个事件聚合器,应该是一个可访问的公共区域,例14为它做了一个core的项目.代码很简单: using Prism.Events; namespac ...
- 2022-06-02:一开始在0位置,每一次都可以向左或者向右跳, 第i次能向左或者向右跳严格的i步。 请问从0到x位置,至少跳几次可以到达。 来自字节。 力扣754. 到达终点数字。
2022-06-02:一开始在0位置,每一次都可以向左或者向右跳, 第i次能向左或者向右跳严格的i步. 请问从0到x位置,至少跳几次可以到达. 来自字节. 力扣754. 到达终点数字. 答案2022- ...