public class HotReloader extends Object
A "shell" creates a HotReloader, and obtains app instances by calling
HotReloader.getAppInstance(appClassName)
,
often to handle a task, e.g. to process an incoming http request.
The getAppInstance()
method returns the same instance
(for the same class name) until reloading occurs
-- if some source files are changed, getAppInstance()
may trigger
recompilation and reloading, returning a new app instance created in a new app class loader.
HotReloader is thread-safe, particularly, getAppInstance()
can be invoked concurrently.
The HotReloader maintains a list of HotCompiler
s, added by the shell through
addCompiler()
.
Each compiler is responsible for a certain kind of source files under the source dirs.
If compiler C2 depends on C1 (e.g. C2 consumes the output files of C1), C2 should be added after C1.
When getAppInstance()
is invoked, if some source files are changed,
the corresponding compilers will be invoked to recompile them.
Recompilation may or may not trigger reloading.
The shell class loader (the one that loads the class of this HotReloader instance)
is required to be a URLClassLoader
.
The app class loader is a sibling of the shell class loader, sharing the same parent.
The app class loader is also a URLClassLoader, initially containing the same class paths
as the shell's (usually the JVM's class paths). Additional class paths can be added
to the app class loader by prependClassPath(java.nio.file.Path)
.
The shell and the app share the same classes loaded by the parent class loader, notably all the JRE classes.
In addition, the app needs to share some classes that's loaded by the shell.
Often, the shell interacts with the app instance through an interface, e.g.
HttpHandler
, it's necessary that the app sees the same Class
for that interface, and all interfaces/classes referenced by that interface, recursively.
The shell also needs to share other classes to communicate with the app,
e.g. FiberLocal
.
The shell can specify these classes by addSharedClasses(Class[])
.
When reloading occurs, the previous app class loader and app instances are dereferenced from this HotReloader. Hopefully they will be garbage collected soon.
If an app instance implements AutoCloseable
,
the close()
method will be called upon unloading.
An unloaded app instance may still be functioning for some time, e.g. to continue processing previously accepted http requests.
Threads created by an app instance may persist after unloading; this is very bad,
especially because it likely references the app class loader, hindering garbage collection.
To avoid that, use thread executors with timeouts (even for core threads);
or do necessary cleanup/shutdown actions in the close()
method mentioned above.
Constructor and Description |
---|
HotReloader()
Create a HotReloader instance.
|
Instance Methods | |
---|---|
Object |
getAppInstance(String appClassName)
Get the app instance for the class name.
|
void |
addCompiler(HotCompiler compiler,
boolean reload,
Path... srcDirs)
Add a compiler.
|
void |
addSharedClasses(Class<?>... sharedClasses)
Add shared classes.
|
void |
prependClassPath(Path path)
Prepend a class path to the app class loader.
|
HotReloader |
onJavaFiles(String... srcDirs)
Reload on java file changes.
|
HotReloader |
onClassFiles()
Reload on class file changes.
|
HotReloader |
onFiles(String fileDesc,
String filePattern,
String... srcDirs)
Reload on file changes.
|
Consumer<CharSequence> |
getMessageOut()
Where diagnosis messages will be printed to.
|
void |
setMessageOut(Consumer<CharSequence> msgOut)
Set where diagnosis messages will be printed to.
|
public HotReloader()
The instance is not very useful yet; call addCompiler()
and addSharedClasses()
etc to set it up.
public Object getAppInstance(String appClassName) throws Exception
The same instance will be returned for the same class name, until reloading occurs, then a new instance will be created in a new class loader.
The app class must have a public 0-arg constructor.
Exception
public void addCompiler(HotCompiler compiler, boolean reload, Path... srcDirs)
If reload=true
, reloading is needed after this compiler recompiles some source files.
It is possible that a recompilation does not require reloading, because the app can handle the
new outputs of the compiler on-the-fly, without being reloaded.
See also convenience methods that add compilers:
onJavaFiles()
, onClassFiles()
, onFiles()
.
public void addSharedClasses(Class<?>... sharedClasses)
Dependant classes referenced in the public/protected APIs of sharedClasses
will be added as shared classes as well (recursively).
public void prependClassPath(Path path) throws Exception
path
- refers to a class dir or a jar fileException
public HotReloader onJavaFiles(String... srcDirs) throws Exception
This method adds a JavacCompiler
. If java files under the srcDirs
are changed, they will be recompiled, and reloading will occur.
The javac options are "-g -parameters"
.
The source directory structure must follow the package structure.
Each java file must contain only one top-level class.
For example, class "foo.Bar"
must be defined in a "foo/Bar.java" file
under one of the srcDirs
.
The output dir for class files is determined by
JavacCompiler.tmpOutDirFor(srcDirs)
.
The directory will be cleaned before the first compilation.
Exception
public HotReloader onClassFiles()
If class files under the JVM classpath are changed, reloading will occur.
public HotReloader onFiles(String fileDesc, String filePattern, String... srcDirs)
If files under srcDirs
matching the filePattern
are changed,
reloading will occur.
The format of filePattern
is specified in
FileSystem.getPathMatcher(String)
.
Example use case: If the app reads ".properties"
files
on startup and cache the information in memory,
it needs to be reloaded if some of the files are changed.
hotReloader.onFiles("prop file", "glob:**.properties", SRC_DIR)
fileDesc
- description of the file type, used for diagnosis outputsVoidCompiler
public Consumer<CharSequence> getMessageOut()
By default the messages will be printed to System.out
.
Call setMessageOut(Consumer)
to change that.
public void setMessageOut(Consumer<CharSequence> msgOut)
Examples:
setMessageOut(System.err::println); setMessageOut( msg->{} ); // silence!