◀Table of Contents
Truffle on Java HotSwap Plugin API
With Java on Truffle you can benefit from enhanced HotSwap capabilites that allow the code to evolve naturally during development without the need for restarting a running application. While code reloading (HotSwap) is a powerful tool, it is not sufficient to reflect all kinds of changes, e.g., changes to annotations, framework-specific changes such as implemented services or beans. For these things the code often needs to be executed to reload configurations or contexts before the changes are fully reflected in the running instance. This is where the Truffle on Java HotSwap Plugin API comes in handy. The Truffle on Java HotSwap Plugin API is meant for framework developers by setting up appropriate hooks to reflect changes in response to source code edits in your IDE. The main design principle is that you can register various HotSwap listeners that will be fired on specified HotSwap events. Examples include the ability to re-run a static initializer, a generic post HotSwap callback and hooks when implementations for a certain service provider changes. Note: The HotSwap Plugin API is under development and more fine-grained registration of HotSwap listeners are likely to be added upon requests from the community. You are welcomed to send enhancement requests to help shape the API through our community support channels.
Review the HotSwap Plugin API by going through a running example that will enable more powerful reloading support on Micronaut.
Micronaut HotSwap Plugin
The Micronaut HotSwap plugin example implementation is hosted as a fork of Micronaut-core. The following instructions are based on a macOS X setup and only minor variations are needed for Windows. To get started:
- Clone the repository:
git clone git@github.com:javeleon/micronaut-core.git
- Build and publish to local Maven repository:
cd micronaut-core ./gradlew publishMavenPublicationToMavenLocal
Now you will have a HotSwap-ready version of Micronaut. Before setting up a sample application that uses the enhanced version of Micronaut, look at what the plugin does under the hood. The interesting class is
MicronautHotSwapPlugin
which holds on to an application context that can be reloaded when certain changes are made to the application source code. The class looks like this: 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); } }
The logic regarding the HotSwap API sits in the constructor of this class. Micronaut is architected around compile-time annotation processing where annotation metadata is gathered and stored into static fields in generated classes. Whenever a developer makes a change to a Micronaut-annotated class, the corresponding metadata classes are re-generated. Since standard HotSwap does not (and it should not) re-run static initializers, with HotSwap Plugin static initializer are re-run for all classes that provide metadata (the Micronaut-generated classes). Thus, this API method
EspressoHotSwap.registerClassInitHotSwap
is used: public static boolean registerClassInitHotSwap(Class<?> klass, boolean onChange, HotSwapAction action)
This will register a listener on Class changes for the specific class and importantly any subclass thereof. The
onChange
variable instructs if static initializers should only be re-run if the code within changed. Theaction
parameter is a hook for firing a specific action whenever a static initializer has been re-run. Here we pass a function for setting theneedsBeenRefresh
field to true whenever an static initializer is re-run. Upon completion of a HotSwap action the plugin receives apostHotSwap
call that, in response to a trueneedsBeenRefresh
, executes the Micronaut-specific code to reload the application context in thereloadContext
method. Detecting and Injecting New Classes
HotSwap is designed to enable classes to be hotswapped in a running application. However, if a developer introduces an entirely new class, e.g., a new
@Controller
class in Micronaut, HotSwap does not magically inject a new class, as doing so would require knowledge about internal classloading logic at the very least. A standard way in which classes are discovered by a framework is through theServiceLoader
mechanism. The Truffle on Java HotSwap API has built-in support for registering service implementation change listeners by means of the methodEspressoHotSwap.registerMetaInfServicesListener
: public static boolean registerMetaInfServicesListener(Class<?> serviceType, ClassLoader loader, HotSwapAction action)
The current support is limited to listening for implementation changes for class path based service deployment in
META-INF/services
. Whenever there is a change to the set of service implementations for the registered class type, theaction
is fired. In the Micronaut HotSwap plugin,reloadContext
is executed which will then pickup the changes automatically. Note: HotSwap actions caused by changes to service implementation changes are fired indepent of HotSwap. As a developer, you do not need to perform a HotSwap from your IDE to see the new functionality in the running application. Next-Level HotSwap for Micronaut
Now that you know how the Micronaut HotSwap plugin works, use this feature in a real application. Here is a sample application created from the tutorial “Creating your first Micronaut Graal Application”. Example’s sources can be downloaded as a ready-made Gradle project from here. Download, unzip and open the project in your IDE.
Before you proceed, make sure that you have Java on Truffle installed and set the GraalVM as the project SDK.
- In your IDE navigate to the root
build.gradle
within the sample project. Add:
run.jvmArgs+="-truffle"
- Also add maven local repository where we previously published the enhanced Micronaut framework. For example:
repositories { mavenLocal() ... }
- In
gradle.properties
update the Micronaut version that you published. For example: micronautVersion=2.5.8-SNAPSHOT
Now you are all setup.
- Execute
assemble
task and create a run configuration using the definedrun
gradle task. -
Press the Debug button to start the application in debugging mode, which enables enhanced HotSwap support.
- Once the application is started, verify that you get a response from the
ConferenceController
by going tohttp://localhost:8080/conferences/random
. - Try to make various changes to the classes within the sample app, e.g., change the
@Controller
mapping to a different value, or add a new@Get
annotated method and apply HotSwap to see the magic. In case you define a new@Controller
class, all you need is compiling the class and once the change is picked up by the file system watch, you will see the reload without the need for explicitly HotSwap.