写点什么

Spring Loaded 代码热更新实践和原理分析 | 京东云技术团队

  • 2023-07-05
    北京
  • 本文字数:2141 字

    阅读完需:约 7 分钟

Spring Loaded代码热更新实践和原理分析 | 京东云技术团队

1、引言

开发者在编码效率和快速迭代中的痛点场景包括:


  1. 修改代码后,需要频繁重启应用,导致开发效率低下;

  2. 实时调试时,不能立即看到代码修改的结果;

  3. 大型项目中,重启的时间成本较高。


针对这些问题,本文将深入探讨如何利用 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。这个字段用于表示当前类的可重载类型,它包含了当前类的最新字节码和其他相关信息。


  • rset 方法是用于获取实例字段的值的方法,处理字段的拦截和替换。

  • ___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 原理


•如何自己实现一个热更新功能呢?思路大同小异,实现各有千秋。如何自己实现一个热加载?如何定义自己的类加载器?


作者:京东零售 程啸

来源:京东云开发者社区

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Spring Loaded代码热更新实践和原理分析 | 京东云技术团队_spring_京东科技开发者_InfoQ写作社区