JavaServer Pages (JSP) is a more flexible technology than servlets because it can respond to dynamic changes at runtime. Can you imagine a common Java class that has this dynamic capability too? It would be interesting if you could modify the implementation of a service without redeploying it and update your application on the fly.
The article explains how to write dynamic Java code. It discusses runtime source code compilation, class reloading, and the use of the Proxy design pattern to make modifications to a dynamic class transparent to its caller.
Let's start with an example of dynamic Java code that illustrates what true dynamic code means and also provides some context for further discussions. Please find this example's complete source code in Resources.
The example is a simple Java application that depends on a service called Postman. The Postman service is described as a Java interface and contains only one method, deliverMessage()
:
public interface Postman {void deliverMessage(String msg);}
A simple implementation of this service prints messages to the console. The implementation class is the dynamic code. This class, PostmanImpl
, is just a normal Java class, except it deploys with its source code instead of its compiled binary code:
public class PostmanImpl implements Postman {
private PrintStream output;public PostmanImpl() {output = System.out;}public void deliverMessage(String msg) {output.println("[Postman] " + msg);output.flush();}}
The application that uses the Postman service appears below. In the main()
method, an infinite loop reads string messages from the command line and delivers them through the Postman service:
public class PostmanApp {
public static void main(String[] args) throws Exception {BufferedReader sysin = new BufferedReader(new InputStreamReader(System.in));
// Obtain a Postman instancePostman postman = getPostman();
while (true) {System.out.print("Enter a message: ");String msg = sysin.readLine();postman.deliverMessage(msg);}}
private static Postman getPostman() {// Omit for now, will come back later}}
Execute the application, enter some messages, and you will see outputs in the console such as the following (you can download the example and run it yourself):
[DynaCode] Init class sample.PostmanImplEnter a message: hello world[Postman] hello worldEnter a message: what a nice day![Postman] what a nice day!Enter a message:
Everything is straightforward except for the first line, which indicates that the class PostmanImpl
is compiled and loaded.
PostmanImpl
's source code. The new implementation // MODIFIED VERSIONpublic class PostmanImpl implements Postman {
private PrintStream output;// Start of modificationpublic PostmanImpl() throws IOException {output = new PrintStream(new FileOutputStream("msg.txt"));}// End of modification
public void deliverMessage(String msg) {output.println("[Postman] " + msg);
output.flush();}}
[DynaCode] Init class sample.PostmanImplEnter a message: hello world[Postman] hello worldEnter a message: what a nice day![Postman] what a nice day!Enter a message: I wanna go to the text file.[DynaCode] Init class sample.PostmanImplEnter a message: me too!Enter a message:Notice
[DynaCode] Init class sample.PostmanImpl
appears again, indicating that the classPostmanImpl
is recompiled
and reloaded. If you check the text file msg.txt (under the working directory), you will see the following:[Postman] I wanna go to the text file.[Postman] me too!
Amazing, right? We are able to update the Postman service at runtime, and the change is completely transparent to
the application. (Notice the application is using the same Postman instance to access both versions of the
implementations.)Four steps towards dynamic code
Let me reveal what's going on behind the scenes. Basically, there are four steps to make Java code dynamic:
To start writing some dynamic code, the first question we have to answer is, "Which part of the code should be
dynamic—the whole application or just some of the classes?" Technically, there are few restrictions. You can
load/reload any Java class at runtime. But in most cases, only part of the code needs this level of flexibility.
The Postman example demonstrates a typical pattern on selecting dynamic classes. No matter how a system is
composed, in the end, there will be building blocks such as services, subsystems, and components. These
building blocks are relatively independent, and they expose functionalities to each other via predefined
interfaces. Behind an interface, it is the implementation that is free to change as long as it conforms
to the contract defined by the interface. This is exactly the quality we need for dynamic classes. So simply put:
Choose the implementation class to be the dynamic class.
For the rest of the article, we'll make the following assumptions about the chosen dynamic classes:
Please note that these assumptions are not prerequisites. They exist just to make the realization of dynamic code
a bit easier so we can focus more on the ideas and mechanisms.
With the selected dynamic classes in mind, deploying the source code is an easy task. Figure 1 shows the file
structure of the Postman example.
Figure 1. The file structure of the Postman example
We know "src" is source and "bin" is binary. One thing worth noting is the dynacode directory, which holds the
source files of dynamic classes. Here in the example, there's only one file—PostmanImpl.java. The bin and dynacode
directories are required to run the application, while src is not necessary for deployment.
Detecting file changes can be achieved by comparing modification timestamps and file sizes. For our example, a check to PostmanImpl.java is performed every time a method is invoked on the Postman
interface. Alternatively, you may spawn a daemon thread in the background to regularly check the file changes. That may result in better performance for large-scale applications.
After a source code change is detected, we come to the compilation issue. By delegating the real job to an existing Java compiler, runtime compilation can be a piece of cake. Many Java compilers are available for use, but in this article, we use the Javac compiler included in Sun's Java Platform, Standard Edition (Java SE is Sun's new name for J2SE).
At the minimum, you can compile a Java file with just one statement, providing that the tools.jar, which contains the Javac compiler, is on the classpath (you can find the tools.jar under <JDK_HOME>/lib/):
int errorCode = com.sun.tools.javac.Main.compile(new String[] {"-classpath", "bin","-d", "/temp/dynacode_classes","dynacode/sample/PostmanImpl.java" });
The class com.sun.tools.javac.Main
is the programming interface of the Javac compiler. It provides static methods to compile Java source files. Executing the above statement has the same effect as running javac
from the command line with the same arguments. It compiles the source file dynacode/sample/PostmanImpl.java using the specified classpath bin and outputs its class file to the destination directory /temp/dynacode_classes. An integer returns as the error code. Zero means success; any other number indicates something has gone wrong.
The com.sun.tools.javac.Main
class also provides another compile()
method that accepts an additional PrintWriter
parameter, as shown in the code below. Detailed error messages will be written to the PrintWriter
if compilation fails.
// Defined in com.sun.tools.javac.Mainpublic static int compile(String[] args);public static int compile(String[] args, PrintWriter out);
I assume most developers are familiar with the Javac compiler, so I'll stop here. For more information about how to use the compiler, please refer to Resources.
The compiled class must be loaded before it takes effect. Java is flexible about class loading. It defines a comprehensive class-loading mechanism and provides several implementations of classloaders. (For more information on class loading, see Resources.)
The sample code below shows how to load and reload a class. The basic idea is to load the dynamic class using our own URLClassLoader
. Whenever the source file is changed and recompiled, we discard the old class (for garbage collection later) and create a new URLClassLoader
to load the class again.
// The dir contains the compiled classes.File classesDir = new File("/temp/dynacode_classes/");
// The parent classloaderClassLoader parentLoader = Postman.class.getClassLoader();
// Load class "sample.PostmanImpl" with our own classloader.URLClassLoader loader1 = new URLClassLoader(new URL[] { classesDir.toURL() }, parentLoader);Class cls1 = loader1.loadClass("sample.PostmanImpl");Postman postman1 = (Postman) cls1.newInstance();
/** Invoke on postman1 ...* Then PostmanImpl.java is modified and recompiled.*/
// Reload class "sample.PostmanImpl" with a new classloader.URLClassLoader loader2 = new URLClassLoader(new URL[] { classesDir.toURL() }, parentLoader);Class cls2 = loader2.loadClass("sample.PostmanImpl");Postman postman2 = (Postman) cls2.newInstance();
/** Work with postman2 from now on ...* Don't worry about loader1, cls1, and postman1* they will be garbage collected automatically.*/
parentLoader
when creating your own classloader. Basically, the rule is that the parent classloader must provide all the dependencies the child classloader requires. So in the sample code, the dynamic class PostmanImpl
depends on the interface Postman
; that's why we use Postman
's classloader as the parent classloader. We are still one step away to completing the dynamic code. Recall the example introduced earlier. There, dynamic class reload is transparent to its caller. But in the above sample code, we still have to change the service instance from postman1
to postman2
when the code changes. The fourth and final step will remove the need for this manual change.
How do you access the up-to-date dynamic class with a static reference? Apparently, a direct (normal) reference to a dynamic class's object will not do the trick. We need something between the client and the dynamic class—a proxy. (See the famous book Design Patterns for more on the Proxy pattern.)
Here, a proxy is a class functioning as a dynamic class's access interface. A client does not invoke the dynamic class directly; the proxy does instead. The proxy then forwards the invocations to the backend dynamic class. Figure 2 shows the collaboration.
Figure 2. Collaboration of the proxy
When the dynamic class reloads, we just need to update the link between the proxy and the dynamic class, and the client continues to use the same proxy instance to access the reloaded class. Figure 3 shows the collaboration.
Figure 3. Collaboration of the proxy with reloaded dynamic class
In this way, changes to the dynamic class become transparent to its caller.
The Java reflection API includes a handy utility for creating proxies. The class java.lang.reflect.Proxy
provides static methods that let you create proxy instances for any Java interface.
The sample code below creates a proxy for the interface Postman
. (If you aren't familiar with java.lang.reflect.Proxy
, please take a look at the Javadoc before continuing.)
InvocationHandler handler = new DynaCodeInvocationHandler(...);Postman proxy = (Postman) Proxy.newProxyInstance(Postman.class.getClassLoader(),new Class[] { Postman.class },handler);
The returned proxy
is an object of an anonymous class that shares the same classloader with the Postman
interface (the newProxyInstance()
method's first parameter) and implements the Postman
interface (the second parameter). A method invocation on the proxy
instance is dispatched to the handler
's invoke()
method (the third parameter). And handler
's implementation may look like the following:
public class DynaCodeInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// Get an instance of the up-to-date dynamic classObject dynacode = getUpToDateInstance();// Forward the invocationreturn method.invoke(dynacode, args);}}
The invoke()
method obtains an instance of the up-to-date dynamic class and forwards the invocation to it. This may involve source code compilation and class reloading if the dynamic class's source file has been modified.
deliverMessage()
through the proxy. Each invocation on the proxy is dispatched to the DynaCodeInvocationHandler
class's invoke()
method. In that method, the latest service implementation is first obtained, which may involve necessary source compilation and class reloading. Then, the invocation forwards to the implementation for real processing. We have gone through all the tricks required for the dynamic Java code. It's time to put them together to build something reusable.
We can build a utility to ease the adoption of dynamic code by encapsulating the four steps mentioned above. The Postman example relies on such a utility called DynaCode
. Remember the PostmanApp
application and its omitted getPostman()
method? It's time to show it:
public class PostmanApp {
public static void main(String[] args) throws Exception {// ......}
private static Postman getPostman() {DynaCode dynacode = new DynaCode();dynacode.addSourceDir(new File("dynacode"));return (Postman) dynacode.newProxyInstance(Postman.class,"sample.PostmanImpl");}}
See how the getPostman()
method creates a dynamic Postman
service, creates an instance of DynaCode
, specifies a source directory, and returns a proxy of some dynamic implementation. To use your own dynamic Java code, you simply need to write three lines of code. Everything else is taken care of inside DynaCode
. Try it yourself (the DynaCode
's source code is included in the example).
I won't go into further details of DynaCode
, but as a review of the technologies we've talked about, take a look at the sequence diagram in Figure 4 to see how DynaCode
works at a high level.
Figure 4. Sequence diagram of DynaCode. Click on thumbnail to view full-sized image.
In this article, I introduced the idea of dynamic Java code and the steps required to realize it. I covered topics such as runtime source compilation, class reloading, and the Proxy design pattern. Though none of these topics are new, by putting them together, we create an interesting dynamic feature for common Java classes so they can be modified and refreshed at runtime just like JSP pages.
An example is provided for demonstration purposes in Resources. It includes a reusable utility called DynaCode
that makes writing your own dynamic code much easier.
I'd like to conclude by discussing the values and applications of dynamic code: Dynamic code can respond quickly to on-demand change requests. It can be used to implement truly dynamic services and business rules that change from time to time, and to replace embedded scripts used by task nodes of workflows. Dynamic code also eases application maintenance and greatly reduces outages caused by application redeployments.