templates.plugins.spincast-hotswap.spincast-hotswap.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{% extends "../../layout.html" %}
{% block sectionClasses %}plugins hasBreadCrumb plugins-spincast-hotswap{% endblock %}
{% block meta_title %}Plugins - Spincast HotSwap{% endblock %}
{% block meta_description %}Spincast HotSwap plugin - classes redefinitions and files modifications listeners.{% endblock %}
{% block scripts %}
{% endblock %}
{% block body %}
Overview
This plugin helps you configure your application for Hot Swapping
(also called
"Hot Reloading") and let you easily register listeners
so you are able to run custom code when modifications are made on classes or on regular files.
Hot Swapping is really useful during development: you change a class and the
modifications are immediately available, without having to restart the application
or the server. This leads to a good development experience!
Java doesn't provide hot swapping out-of-the-box, and this is why
third-party solutions have emerged.
The most known of those solutions is JRebel
which is a very nice product but that is way, way too expensive. From the other available
solutions, the more mature is HotswapAgent + DCEVM,
which is free and works very well too!
It is the solution used by this plugin...
In short, here's what this plugin has to offer:
-
Help with installing DCEVM and HotswapAgent
Those two are both required for class redefinitions and associated listeners to work.
-
Classes modifications listening mechanism
In addition to
classes being automatically reloaded by HotswapAgent, you
can register some listeners to be called when
classes are modified. This allows you to run custom code
to react to modifications (for example by clearing
some cache, reloading routes, etc.).
-
Files modifications listening mechanism
HotswapAgent is about classes redefinition, but sometimes you also
need to react to modifications made to regular files (
.yaml
or .properties
files, for
example). This plugin provides an easy way to register listeners
for files modifications.
Usage
Classes redefinitions
HotswapAgent, when properly installed, will make most of the changes made to
your code available immediately, without having to restart the application!
In addition to this, this current plugin allows you to register listeners,
that will be called when classes are redefined by HotswapAgent.
Note that if the application is not started with the HotswapAgent agent,
all features related to classes redefinitions and the associated listeners will be
automatically disabled. This is the behavior you want for an application running
on any environment other than locally. Classes redefinitions is indeed something
in general only done during development.
Registering a class redefinition listener
You can register listeners to be called when some specific classes are
redefined by HotswapAgent. To do so, you simply bind them in your application
Guice module using the associated HotSwapClassesRedefinitionsListener
multibinder:
Multibinder<HotSwapClassesRedefinitionsListener> multibinder =
Multibinder.newSetBinder(binder(), HotSwapClassesRedefinitionsListener.class);
multibinder.addBinding().to(AppMainClassClassesRedefinitionsListener.class)
.in(Scopes.SINGLETON);
A classes redefinitions listener (such as "AppMainClassClassesRedefinitionsListener
" in the above code) is
a class that implements
the HotSwapClassesRedefinitionsListener
interface. There are three methods to provide when you create such listeners:
-
Set<Class<?>> getClassesToWatch()
This is where you specify for which classes you want to be called, when
modifications are made. A listener can be interested in a single class or in
multiple classes.
-
void classRedefined(Class<?> redefinedClass)
The method that is called when a class is modified. This is your hook!
In this method, you can reload/call some code to make use of the newly redefined
class.
-
boolean isEnabled()
The listener will only be registered if this method returns
true
.
Note that all classes redefinitions listeners are automatically
disabled if the application is started without
HotswapAgent! But this method allows you fine grain control,
during development.
Classes redefinitions listener example
Here's an example of a classes redefinitions listener. It listens to changes to an
"AppRoutes
" class which may be the place where the routes of
an application are defined. When this
class changes, the listener makes sure the routes cache is cleared and routes are reloaded!
public class AppMainClassClassesRedefinitionsListener implements HotSwapClassesRedefinitionsListener {
private final AppRouter appRouter;
@Inject
public AppMainClassClassRedefinitionListener(AppRouter appRouter) {
this.appRouter = appRouter;
}
protected AppRouter getAppRouter() {
return this.appRouter;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Set<Class<?>> getClassesToWatch() {
return Sets.newHashSet(AppRoutes.class);
}
@Override
public void classRedefined(Class<?> redefinedClass) {
getAppRouter().clearCacheAndReloadRoutes();
}
}
Explanation :
-
1 : The listener implements
HotSwapClassesRedefinitionsListener.
-
14-17 : The listener is always enabled
(as long as the application was started with
HotswapAgent
!).
-
19-22 : We are only interested in modifications
made to the
AppRoutes
class, which is responsible (in this example) for defining
the application's routes.
-
24-27 : When the
AppRoutes
class
is modified, the classRedefined()
method is called. In this method, we tell our
router
to reload the cached routes. Note that we only
watch one class ("AppRoutes
") in this example, so there is no need to use
the "redefinedClass
" parameter to validate if the current modification
has been made on that class or on another!
Remember that you may register as many listeners as you want. Each can listen to a different
class or they may share some. If one of your listeners is interested in more than
one class, you then may have to use the "redefinedClass
" parameter, provided
when the classRedefined()
method is called, to validate which class
was actually modified!
Files modifications
In addition to classes redefinitions listeners, this plugin also provides an easy way
of listening to modifications made on regular files.
For example, during development, you may change a app-config.yaml
file to tweak some configurations of your application.
By registering a listener for this file, you would be able to refresh
the AppConfigs
object uses to access the configurations in your Java code.
Registering a file modifications listener
To register a new files modifications listener, you simply bind it in your application
Guice module using the associated HotSwapFilesModificationsListener
multibinder:
Multibinder<HotSwapFilesModificationsListener> multibinder =
Multibinder.newSetBinder(binder(), HotSwapFilesModificationsListener.class);
multibinder.addBinding().to(AppConfigFileModificationsListener.class)
.in(Scopes.SINGLETON);
A files modifications listener (such as "AppConfigFileModificationsListener
" in the above example), is
a class that implements
HotSwapFilesModificationsListener.
There are three methods to provide when you create such listener:
-
Set<FileToWatch> getFilesToWatch()
This is where you specify which files you want to watch. A listener can be interested in
a single file or in multiple files. The files can be on the file system or on the classpath.
We'll have a look at FileToWatch
and how to define the watched files soon.
-
void fileModified(File modifiedFile)
The method is called when a file is modified. This is your hook!
For example, you can programmatically delete some cache in your application.
-
boolean isEnabled()
The listener will only be registered if this method returns
true
. Most of the time, you only want to listen to
files modifications during development.
A common pattern is therefore to make use of
isDevelopmentMode() :
@Override
public boolean isEnabled() {
return getSpincastConfig().isDevelopmentMode();
}
Files modifications listener example
Here's an example of a files modifications listener. It listens on changes to
the "app-configs.yaml
" configurations file.
When this file changes, it then clears the configurations cache so the new
values are used:
public class AppConfigFileModificationsListener implements HotSwapFilesModificationsListener {
private final AppConfigs appConfigs;
@Inject
public AppConfigFileModificationListener(AppConfigs appConfigs) {
this.appConfigs = appConfigs;
}
protected AppConfigs getAppConfigs() {
return this.appConfigs;
}
@Override
public boolean isEnabled() {
return getAppConfigs().isDevelopmentMode();
}
@Override
public Set<FileToWatch> getFilesToWatch() {
return Sets.newHashSet(FileToWatch.ofClasspath("app-config.yaml"));
}
@Override
public void fileModified(File modifiedFile) {
getAppConfigs().clearConfigCache();
}
}
Explanation :
-
1 : The listener implements
HotSwapFilesModificationsListener.
-
14-17 : The listener will be enabled only
during development!
-
19-22 : We are interested by changes made to
the "
app-config.yaml
" file, which is located at the root of the
classpath. In the next section, we'll learn how to use FileToWatch
to define the files to watch!
-
24-27 : When the "
app-config.yaml
" file
is modified, we tell AppConfigs
to clear its cache.
Note that, in this example, we only listen to one file so there is no need to use
the "modifiedFile
" parameter to validate what file was modified.
You may have as many listeners as you want. Each one can listen to different
files or share some of them. If one of your listeners is interested in more than
one file, you may have to use the "modifiedFile
" parameter provided
when the fileModified()
method is called, to validate which one exactly
has been modified.
The getFilesToWatch()
method has to return a set of
FileToWatch.
A FileToWatch
is able to specify if the files to watch are on the file system or on the
classpath, and it also allows you to target files using a regular expression.
Let's see the three methods you can use to create an instance of FileToWatch
:
-
FileToWatch ofFileSystem(String fileAbsolutePath)
Creates a FileToWatch
from the absolute path of a file
on the file system. For example:
FileToWatch file = FileToWatch.ofFileSystem("/home/stromgol/dev/some-file-to-watch.json");
-
FileToWatch ofClasspath(String classpathFilePath)
Creates a FileToWatch
from a classpath path. The path can start
with a "/" or not, it doesn't make any difference.
FileToWatch file = FileToWatch.ofClasspath("app-configs.yaml");
-
FileToWatch ofRegEx(String dirPath, String fileNameRegEx, boolean isClassPath)
This factory is the most powerful of the three! It allows you to specify a regular expression
to use for the names of the files to watch. Those files can be on the file system or on
the classpath.
Note that using a regular expression is available for the names of the files, but you
still have to specify the directory where those files are located.
FileToWatch fileToWatch = FileToWatch.ofRegEx("/home/stromgol/news/",
"news-[0-9]+\\.yaml",
false);
As you can see in this example, a FileToWatch
may represent more than one file! Here, any files
matching the provided regular expression (such as "news-12.yaml
",
"news-687.yaml
", etc.) will be watched. In this situation, you may have to
check what file actually changed by using the modifiedFile
parameter provided when the fileModified()
hook is
called!
Installation
Installing the plugin itself is very simple, but enabling
HotswapAgent and
DCEVM is a little bit trickier.
The time you spend installing those will be well rewarded, as you'll then have
in place the best free Java Hot Swapping solution out there!
The plugin itself
1.
Add this Maven artifact to your project:
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-plugins-hotswap</artifactId>
<version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>
2. Add an instance of the SpincastHotSwapPlugin
plugin to your Spincast Bootstrapper:
{% verbatim %}
Spincast.configure()
.plugin(new SpincastHotSwapPlugin())
// ...
{% endverbatim %}
DCEVM
1.
Spincast currently requires Java 8 and therefore the way to install
DCEVM
is by downloading a binary for the Java8 version you
are using from this page.
For example: "DCEVM-8u181-installer.jar
".
2. You launch this .jar (by double-clicking it or from a terminal). This will
open a window allowing you to install the DCEVM patch on one or many of your Java
installations:
You choose "Install DCEVM as altvm" on the JDK you want to patch.
Note that you should probably only install DCEVM locally, for
development purposes.
Hotswap Agent
1. You download the latest Hotswap Agent version
and save it somewhere on your computer (we'll use "C:\hotswap-agent-1.3.0.jar
" as the path, on a Windows machine, in this example).
2. When you start your application during development (most of the time inside an IDE), you add those
two VM arguments: "-XXaltjvm=dcevm -javaagent:C:\hotswap-agent-1.3.0.jar
".
Here's an example in Eclipse:
And that's it!
When you start your application with this in place, any modification to a class will
make this class being automatically redefined. And if you have registered some listeners,
they will be called.
Make sure you read the HotswapAgent documentation
if you have issues.
Note that the steps required to use DCEVM
and HotswapAgent
are different
starting with Java9+! With those versions, we'll have to use an
already patched distribution of
OpenJDK.
We'll update this plugin documentation as soon as Spincast supports Java 9+.
{% endblock %}