Espresso 增强 HotSwap 功能

使用 Espresso,您可以受益于增强的 HotSwap 功能,这些功能允许代码在开发过程中自然地演变,而无需重新启动正在运行的应用程序。除了以调试模式启动应用程序并将标准 IDE 调试器附加到应用程序之外,您无需配置任何特定内容即可获得增强型 HotSwap 的优势。

使用 Espresso 调试 #

您可以使用您最喜欢的 IDE 调试器调试在 Espresso 运行时运行的 Java 应用程序。例如,从 IntelliJ IDEA 启动调试器会话基于运行配置。要确保您将调试器附加到同一环境中的 Java 应用程序,请在主菜单中导航至 **运行**、**调试...**、**编辑配置**,展开环境,检查 JRE 值和 VM 选项值。它应该显示 GraalVM 作为项目的 JRE,并且 VM 选项应包含 -truffle -XX:+IgnoreUnrecognizedVMOptions。需要指定 -XX:+IgnoreUnrecognizedVMOptions,因为 Intellij 会自动添加一个 -javaagent 参数,该参数目前尚不支持。按调试。

这将运行应用程序并在后台启动调试器会话。

在调试会话期间使用 HotSwap #

调试器会话运行后,您将能够应用广泛的代码更改(HotSwap),而无需重新启动会话。您可以尝试在自己的应用程序上或按照以下说明进行操作。

  1. 创建一个新的 Java 应用程序。
  2. 使用以下 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);
         }
     }
    
  3. 检查 java.vm.name 属性是否表明您正在 Espresso 上运行。
  4. runDemo() 的第一行放置一个行断点。
  5. 设置运行配置以使用 Espresso 运行,然后按调试。您将看到

    HotSwap Debugging Session: Debug Output

  6. 在断点处暂停时,从 runDemo() 的主体中提取一个方法

    HotSwap Debugging Session: Extract Method

  7. 通过导航至运行 -> 调试操作 -> 重新加载已更改的类来重新加载更改

    HotSwap Debugging Session: Reload Changed Classes

  8. 通过注意到调试 -> 帧视图中的 <已过时>:-1 当前帧来验证更改是否已应用

    HotSwap Debugging Session: Frames View

  9. 在新提取方法的第一行放置一个断点,然后按继续程序。断点将命中

    HotSwap Debugging Session: Set a Breakpoint and Resume Program

  10. 尝试将 printRandom() 的访问修饰符从 private 更改为 public static。重新加载更改。按继续程序以验证更改是否已应用

    HotSwap Debugging Session: Change Access Modifiers

观看有关 Espresso 演示的增强 HotSwap 功能的视频 版本

支持的更改 #

Espresso 的增强 HotSwap 功能几乎已完成。支持以下更改

  • 添加和删除方法
  • 添加和删除构造函数
  • 在接口中添加和删除方法
  • 更改方法的访问修饰符
  • 更改构造函数的访问修饰符
  • 添加和删除字段
  • 更改字段类型
  • 在层次结构中移动字段并保留状态(请参见下面的说明)
  • 对类访问修饰符的更改,例如,abstract 和 final 修饰符
  • 对 Lambda 表达式的更改
  • 添加新的匿名内部类
  • 删除匿名内部类
  • 更改超类
  • 更改已实现的接口

说明:当在类层次结构中移动实例字段时,只要有可能,状态就会保留。例如,向上拉字段重构,其中所有现有源子类的实例都将能够从超类字段中读取先前存储的值。另一方面,对于在更改之前不存在该字段的无关子类实例,新字段的值将是语言默认值(对于对象类型字段为 null,对于 int 为 0,等等)。

以下限制仍然存在

  • 对枚举的更改

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 仅需少量更改。要开始

  1. 克隆存储库
      git clone git@github.com:javeleon/micronaut-core.git
    
  2. 构建并发布到本地 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 调用,该调用会响应 true 的 needsBeenRefresh 执行 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。

  1. 在您的 IDE 中,导航到示例项目中的根 build.gradle。添加
     run.jvmArgs+="-truffle"
    
  2. 还添加我们之前发布增强版 Micronaut 框架的本地 Maven 存储库。例如
     repositories {
     mavenLocal()
     ...
     }
    
  3. gradle.properties 中更新您发布的 Micronaut 版本。例如
     micronautVersion=2.5.8-SNAPSHOT
    

    现在您已经完成所有设置。

  4. 执行 assemble 任务并使用定义的 run Gradle 任务创建一个运行配置。

  5. 按调试按钮以调试模式启动应用程序,这将启用增强型 HotSwap 支持。

  6. 应用程序启动后,通过转到 https://127.0.0.1:8080/conferences/random 验证您是否从 ConferenceController 获取响应。

  7. 尝试对示例应用程序中的类进行各种更改,例如,将 @Controller 映射更改为不同的值,或添加一个新的 @Get 注释方法并应用 HotSwap 以查看神奇的效果。如果您定义了一个新的 @Controller 类,您只需要编译该类,然后一旦文件系统监视器获取到更改,您将看到重新加载,无需显式进行 HotSwap。

与我们联系