Add dynamic Java code to your application

来源:百度文库 编辑:神马文学网 时间:2024/04/29 02:50:34
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.
An example of dynamic Java code
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 inResources.
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 candownload 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.
Now we are ready to see something dynamic. Without stopping the application, let's modify PostmanImpl's source code. The new implementation
delivers all the messages to a text file, instead of the console:
// 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 class PostmanImpl 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:
Deploy selected source code and monitor file changes Compile Java code at runtime Load/reload Java class at runtime Link the up-to-date class to its caller
Deploy selected source code and monitor file changes
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:
The chosen dynamic class implements some Java interface to expose functionality The chosen dynamic class's implementation does not hold any stateful information about its client (similar to the stateless session bean), so the instances of the dynamic class can replace each other
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.
Compile Java code at runtime
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 /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 toResources.
Load/reload Java class at runtime
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, seeResources.)
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.*/
Pay attention to the 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.
Link the up-to-date class to its caller
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 bookDesign 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 theJavadoc 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.
Now we have completed the Postman service's dynamic code. A client creates a proxy of the service and calls the method 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.
Conclusion
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 inResources. 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.
About the author
Li Yang joined IBM in April 2004 and is a Certified IBM Rational Consultant on Rational Unified Process, requirements management, and object-oriented analysis and design. He has experience with Java server-side technologies, Web architectures, Java EE projects, and Java SE system library development.
Resources
Download the Postman example (tested with Sun J2SE 1.4.2)
http://www.javaworld.com/javaworld/jw-06-2006/dynamic/jw-0612-dynamic.zip Javac compiler documentation
http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html "The Basics of Java Classloaders," Chuck McManis (JavaWorld, October 1996)
http://www.javaworld.com/javaworld/jw-10-1996/jw-10-indepth.html Javadoc for java.net.URLClassLoader
http://java.sun.com/j2se/1.4.2/docs/api/java/net/URLClassLoader.html The famous Gang of Four bookDesign Patterns, Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Publishing Co., 1995; ISBN0201633612)
http://www.amazon.com/exec/obidos/ASIN/0201633612/javaworld Javadoc for java.lang.reflect.Proxy
http://java.sun.com/j2se/1.4.2/docs/api/java/lang/reflect/Proxy.html For more articles on design patterns, browse the Design Patterns section of JavaWorld's Topical Index
http://www.javaworld.com/channel_content/jw-patterns-index.shtml For more articles on putting your Java skills to work, browse the Applied Java section of JavaWorld's Topical Index
http://www.javaworld.com/channel_content/jw-applied-index.shtml