- 适用于 JDK 24 的 GraalVM(最新)
- 适用于 JDK 25 的 GraalVM(早期访问)
- 适用于 JDK 21 的 GraalVM
- 适用于 JDK 17 的 GraalVM
- 存档
- 开发构建
使用 Espresso 增强 HotSwap 功能
借助 Espresso,您可以受益于增强的 HotSwap 功能,使代码在开发过程中自然演进,而无需重新启动正在运行的应用程序。除了以调试模式启动应用程序并附加标准 IDE 调试器外,您无需进行任何特定配置即可获得增强的 HotSwap 优势。
使用 Espresso 调试 #
您可以使用自己喜欢的 IDE 调试器来调试在 Espresso 运行时中运行的 Java 应用程序。例如,从 IntelliJ IDEA 启动调试器会话是基于“运行配置”(Run Configurations)的。为确保您将调试器附加到相同环境中的 Java 应用程序,请在主菜单中导航到 运行 (Run),调试… (Debug…),编辑配置 (Edit Configurations),展开“环境”(Environment),检查 JRE 值和 VM 选项值。它应该显示 GraalVM 作为项目的 JRE,并且 VM 选项应包含 -truffle -XX:+IgnoreUnrecognizedVMOptions
。有必要指定 -XX:+IgnoreUnrecognizedVMOptions
,因为 IntelliJ 会自动添加一个目前尚不支持的 -javaagent
参数。按“调试”(Debug)。
这将运行应用程序并在后台启动一个调试器会话。
在调试会话期间使用 HotSwap #
一旦您的调试会话运行起来,您将能够应用广泛的代码更改(HotSwap),而无需重新启动会话。您可以随意在自己的应用程序上尝试,或按照以下说明操作:
- 创建一个新的 Java 应用程序。
- 使用以下
main
方法作为起点:public class HotSwapDemo { private static final int ITERATIONS = 100; public static void main(String[] args) { HotSwapDemo demo = new HotSwapDemo(); System.out.println("Starting HotSwap demo with Espresso: 'java.vm.name' = " + System.getProperty("java.vm.name")); // run something in a loop for (int i = 1; i <= ITERATIONS; i++) { demo.runDemo(i); } System.out.println("Completed HotSwap demo with Espresso"); } public void runDemo(int iteration) { int random = new Random().nextInt(iteration); System.out.printf("\titeration %d ran with result: %d\n", iteration, random); } }
- 检查
java.vm.name
属性是否显示您正在 Espresso 上运行。 - 在
runDemo()
的第一行放置一个行断点。 -
设置运行配置以使用 Espresso 运行并按“调试”(Debug)。您将看到:
-
在断点处暂停时,从
runDemo()
的主体中提取一个方法。 -
通过导航到“运行”(Run)->“调试操作”(Debugging Actions)->“重新加载更改的类”(Reload Changed Classes)来重新加载更改。
-
通过注意“调试”(Debug)->“帧”(Frames)视图中的
<obsolete>:-1
当前帧来验证更改已应用。 -
在新提取的方法的第一行放置一个断点,然后按“恢复程序”(Resume Program)。断点将命中。
-
尝试将
printRandom()
的访问修饰符从private
更改为public static
。重新加载更改。按“恢复程序”(Resume Program)验证更改已应用。
观看 使用 Espresso 增强 HotSwap 功能演示的视频版本。
支持的更改 #
Espresso 的增强 HotSwap 几乎功能完备。支持以下更改:
- 添加和删除方法
- 添加和删除构造函数
- 从接口添加和删除方法
- 更改方法的访问修饰符
- 更改构造函数的访问修饰符
- 添加和删除字段
- 更改字段类型
- 在继承层级中移动字段并保留状态(参见下面的注释)
- 更改类访问修饰符,例如,abstract 和 final 修饰符
- 对 Lambda 表达式的更改
- 添加新的匿名内部类
- 删除匿名内部类
- 更改超类
- 更改已实现的接口
注意:当实例字段在类继承层级中移动时,状态会尽可能地保留。例如,“上拉字段”重构,其中源子类的所有现有实例都将能够从超类字段读取先前存储的值。另一方面,对于更改前字段不存在的无关子类实例,新字段的值将是语言默认值(对象类型字段为 null,int 为 0 等等)。
以下限制仍然存在:
- 对枚举(Enums)的更改
HotSwap 插件 API #
借助 Espresso,您可以受益于增强的 HotSwap 功能,使代码在开发过程中自然演进,而无需重新启动正在运行的应用程序。虽然代码重新加载 (HotSwap) 是一个强大的工具,但它不足以反映所有类型的更改,例如对注解的更改、框架特定的更改(如已实现的服务或 Bean)。对于这些情况,代码通常需要执行以重新加载配置或上下文,然后更改才能在运行实例中完全反映出来。这就是 Espresso HotSwap 插件 API 派上用场的地方。
HotSwap 插件 API 旨在供框架开发者使用,通过设置适当的钩子来响应 IDE 中源代码编辑的更改。主要设计原则是您可以注册各种 HotSwap 监听器,它们将在指定的 HotSwap 事件上触发。示例包括重新运行静态初始化器、通用的 HotSwap 后回调,以及当某个服务提供者的实现发生变化时触发的钩子。
注意:HotSwap 插件 API 正在开发中,可能会根据社区请求添加更细粒度的 HotSwap 监听器注册。欢迎您通过我们的社区支持渠道发送增强请求,以帮助塑造 API。
通过一个运行中的示例来审查 HotSwap 插件 API,该示例将为 Micronaut 提供更强大的重新加载支持。
Micronaut HotSwap 插件 #
Micronaut HotSwap 插件示例实现作为 Micronaut-core 的一个 分支 托管。以下说明基于 macOS X 设置,Windows 仅需微小改动。要开始:
- 克隆仓库
git clone git@github.com:javeleon/micronaut-core.git
- 构建并发布到本地 Maven 仓库
cd micronaut-core ./gradlew publishMavenPublicationToMavenLocal
现在您将拥有一个支持 HotSwap 的 Micronaut 版本。在设置使用增强版 Micronaut 的示例应用程序之前,先了解一下插件在幕后做了什么。
有趣的类是 MicronautHotSwapPlugin
,它持有一个应用程序上下文,当应用程序源代码发生某些更改时,该上下文可以重新加载。该类如下所示:
final class MicronautHotSwapPlugin implements HotSwapPlugin {
private final ApplicationContext context;
private boolean needsBeenRefresh = false;
MicronautHotSwapPlugin(ApplicationContext context) {
this.context = context;
// register class re-init for classes that provide annotation metadata
EspressoHotSwap.registerClassInitHotSwap(
AnnotationMetadataProvider.class,
true,
() -> needsBeenRefresh = true);
// register ServiceLoader listener for declared bean definitions
EspressoHotSwap.registerMetaInfServicesListener(
BeanDefinitionReference.class,
context.getClassLoader(),
() -> reloadContext());
EspressoHotSwap.registerMetaInfServicesListener(
BeanIntrospectionReference.class,
context.getClassLoader(),
() -> reloadContext());
}
@Override
public String getName() {
return "Micronaut HotSwap Plugin";
}
@Override
public void postHotSwap(Class<?>[] changedClasses) {
if (needsBeenRefresh) {
reloadContext();
}
needsBeenRefresh = false;
}
private void reloadContext() {
if (Micronaut.LOG.isInfoEnabled()) {
Micronaut.LOG.info("Reloading app context");
}
context.stop();
context.flushBeanCaches();
context.start();
// fetch new embedded application bean which will re-wire beans
Optional<EmbeddedApplication> bean = context.findBean(EmbeddedApplication.class);
// now restart the embedded app/server
bean.ifPresent(ApplicationContextLifeCycle::start);
}
}
关于 HotSwap API 的逻辑位于这个类的构造函数中。Micronaut 是围绕编译时注解处理构建的,注解元数据被收集并存储到生成的类中的静态字段中。每当开发人员更改 Micronaut 注解的类时,相应的元数据类就会重新生成。由于标准 HotSwap 不会(也不应该)重新运行静态初始化器,因此借助 HotSwap 插件,所有提供元数据(Micronaut 生成的类)的类的静态初始化器都会重新运行。因此,使用了这个 API 方法 EspressoHotSwap.registerClassInitHotSwap
:
public static boolean registerClassInitHotSwap(Class<?> klass, boolean onChange, HotSwapAction action)
这将在特定类以及任何其子类的类更改上注册一个监听器。onChange
变量指示是否仅在代码内部发生更改时才重新运行静态初始化器。action
参数是一个钩子,用于在静态初始化器重新运行后触发特定的操作。在这里,我们传递一个函数,当静态初始化器重新运行时,将 needsBeenRefresh
字段设置为 true。在 HotSwap 操作完成后,插件会收到一个 postHotSwap
调用,该调用响应 needsBeenRefresh
为 true 的情况,执行 Micronaut 特定代码以在 reloadContext
方法中重新加载应用程序上下文。
检测并注入新类 #
HotSwap 旨在允许类在运行中的应用程序中进行 HotSwap。但是,如果开发人员引入了一个全新的类(例如,Micronaut 中的一个新的 @Controller
类),HotSwap 不会神奇地注入新类,因为这样做至少需要了解内部类加载逻辑。
框架发现类的一种标准方式是通过 ServiceLoader
机制。HotSwap API 通过方法 EspressoHotSwap.registerMetaInfServicesListener
内置支持注册服务实现更改监听器:
public static boolean registerMetaInfServicesListener(Class<?> serviceType, ClassLoader loader, HotSwapAction action)
目前的支持仅限于监听 META-INF/services
中基于类路径的服务部署的实现更改。每当注册类类型的服务实现集发生更改时,action
就会触发。在 Micronaut HotSwap 插件中,会执行 reloadContext
,然后它会自动拾取这些更改。
注意:由服务实现更改引起的 HotSwap 操作是独立于 HotSwap 触发的。作为开发人员,您无需从 IDE 执行 HotSwap 即可在运行中的应用程序中看到新功能。
Micronaut 的下一级 HotSwap #
现在您已经了解 Micronaut HotSwap 插件的工作原理,让我们在实际应用程序中使用此功能。这里有一个从教程 “创建您的第一个 Micronaut Graal 应用程序” 中创建的示例应用程序。示例源代码可以作为现成的 Gradle 项目从 这里 下载。下载、解压缩并在您的 IDE 中打开项目。
在继续之前,请确保您已安装 Espresso 并将 GraalVM 设置为项目 SDK。
- 在您的 IDE 中导航到示例项目中的根
build.gradle
。添加:run.jvmArgs+="-truffle"
- 此外,添加我们之前发布增强型 Micronaut 框架的本地 Maven 仓库。例如:
repositories { mavenLocal() ... }
- 在
gradle.properties
中更新您发布的 Micronaut 版本。例如:micronautVersion=2.5.8-SNAPSHOT
现在您已全部设置完毕。
-
执行
assemble
任务并使用定义的run
Gradle 任务创建运行配置。 -
按下“调试”(Debug)按钮以调试模式启动应用程序,这将启用增强的 HotSwap 支持。
-
应用程序启动后,通过访问
https://:8080/conferences/random
来验证您是否从ConferenceController
获得了响应。 - 尝试对示例应用程序中的类进行各种更改,例如,将
@Controller
映射更改为不同的值,或添加一个新的@Get
注解方法,然后应用 HotSwap 以查看神奇的效果。如果您定义了一个新的@Controller
类,您只需编译该类,一旦文件系统监视器检测到更改,您就会看到重新加载,而无需显式执行 HotSwap。