lua-resty-ffi provides an efficient and generic API to do hybrid programming in openresty with mainstream languages (Go, Python, Java, Rust, Nodejs, etc.).
I already implement code hot-reload for Python and Nodejs in lua-resty-ffi.
The Java class reloading is the last piece of the puzzle.
No out-of-box API to do class reloading, so it’s more tricky.
What is classloader?
All classes in a Java application are loaded using some subclass of
Loading classes are on-demand and lazy, i.e. when the java program starts, it doesn’t load all classes constructing this program.
When a class is loaded, all classes it references are loaded too. This is done by the same classloader for the entry class. This class loading pattern happens recursively, until all classes needed are loaded. This may not be all classes in the application. Unreferenced classes are not loaded until the time they are referenced.
The instances of the same class loaded by different class loaders is in different type.
Class loaders in Java are organized into a hierarchy.
The default class loader chain of openjdk-11:
org.codehaus.mojo.exec.URLClassLoaderBuilder$ExecJavaClassLoader@5aae8eb5 jdk.internal.loader.ClassLoaders$AppClassLoader@659e0bfd jdk.internal.loader.ClassLoaders$PlatformClassLoader@25b342cd
Note that the bootstrap classloader (parent of PlatformClassLoader) is null, so it’s not shown in the chain.
As known, the process of class loading applies the parent delegation model.
When the class is not found, the class loader would try to load class from its parent class loader, if not ok, it turns to do it itself, and so on.
By default, a loaded class is cached forever by JVM.
To reload a class, it has two conditions:
- crate a new class loader to load class each time
- the class loader breaks the parent delegation model
I implement such class loader for lua-resty-ffi, based on this blog.
Let’s see how to satisfy above two conditions.
Check the source code here.
new ClassLoader to load new class
load classes from the child first
With new class loader, the chain becomes:
DynamicClassLoader@55731ba7 jdk.internal.loader.ClassLoaders$AppClassLoader@659e0bfd jdk.internal.loader.ClassLoaders$PlatformClassLoader@23a187ca
Why not Thread.contextClassLoader?
I thought by setting
contextClassLoader of the polling thread, every new class
from that thread would use that context class loader automatically.
But I am wrong. The context class loader should be used explicitly, otherwise, it’s useless.
The fact that the linked classes will be loaded by the same class loader perfectly meets my need, so that I do not need to change the code of each JAVA app, i.e. the class reloading is non-intrusive.
How to use?
Put a question mark as the class suffix when you calls
Please look at the doc in lua-resty-ffi for more details: