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 原理
•如何本人实现一个热更新性能呢?思路大同小异,实现各有千秋。如何本人实现一个热加载?如何定义本人的类加载器?
作者:京东批发 程啸
起源:京东云开发者社区