Build dynamically extensible applications

Find out how to build programs that you can extend dynamically -- even while they execute

Most Java programs are written in this (albeit grossly over-simplified) fashion:

  • A programmer writes, compiles, and debugs a bunch of code.
  • Then he (or, increasingly, she) distributes the completed program.

The point I'm trying to illustrate is that once the program is written, it contains all of the features it will ever have. If new features are ever necessary, the programmer must go back and edit the source code to include the new features, recompile the code, and redistribute the updated program.

The fact that most Java programs are written in this fashion is the result of Java's C and C++ heritage: Both C and C++ are statically linked languages, which means that all of the pieces that make up a C or C++ program must be present at the link step. Couple this with the fact that many Java programmers migrated from a C and/or C++ environment, and you can see why this type of programming is the norm. The thing is, in Java, we are not limited by the language in this way. Java, as you will soon see, allows programs to be dynamically extensible -- that is, to dynamically load in and execute new code, including code that wasn't in existence when the program was written. Sound far-fetched? It's not. What's more, I'm betting you use at least one program like this every day.

To make the distinction clear, I'll call programs written as described at the beginning of the article as "not dynamically extensible." Now, I don't want you to think that not being dynamically extensible is some sort of a flaw. That is not necessarily so. Some programs shouldn't be dynamically extensible. However, many programs, including most shrink-wrapped applications, would benefit users greatly if they were.

Just try to imagine a Web browser written in Java that could not load and execute Java applets, which are dynamically loaded into the browser. The thought is almost comical -- that's essentially the whole reason for writing a browser in Java. A browser, therefore, is a good example of a program that should be dynamically extensible. (Didn't I tell you that you probably used a dynamically extensible program daily?)

I bet you're wondering why this feature is important for programs other than browsers. Well for one thing, if a program is dynamically extensible, you don't need to modify the main body of source code in order to add new features. This is a great blessing for two reasons. First, editing working code can introduce bugs. Second, end users don't need access to the original source code.

Dynamic extensibility is important for another reason, as well. If the program is written correctly (and I'll show you how), you don't even have to restart the program to pick up the additions. Consider a browser once again. If it encounters an applet tag in an HTML document, it locates the applet code, loads it, and executes it without the user ever having to exit the browser. This is a good thing.

The technique I'm about to describe will work under both Java 1.1 and Java 1.0. It doesn't use JavaBeans, class introspection, or class serialization (APIs provided only with Java 1.1). Instead, it makes use of features that have been in the language and the class library since the beginning.

The tools you'll need

Let's stop for a moment and look at the relevant pieces in the Java class library.

java.lang.Class

Instances of class Class represent classes and interfaces in a Java application. The Java virtual machine (JVM) usually manipulates classes behind the scenes; however, a program can directly manipulate classes via instances of the Class class.

Here is the public interface to class Class.

public final class java.lang.Class extends java.lang.Object
{
    public static Class forName(String className);
    public ClassLoader getClassLoader();
    public Class [] getInterfaces();
    public String getName();
    public Class getSuperclass();
    public boolean isInterface();
    public Object newInstance();
    public String toString();
}

We only need to look at the forName() and newInstance() methods. We'll deal with the forName() method first.

static Class forName(String className)

The forName() method returns the Class instance associated with the class with the specified class name. It loads the class into the executing program via the currently active class loader. The className parameter can name any class -- even one that does not exist currently. If the forName() method cannot find the named class, it throws a ClassNotFoundException.

Now let's take a look at the newInstance() method.

Object newInstance()

The newInstance() method creates a newly allocated instance of the class represented by the Class instance. Because this is done exactly as if by a new expression with an empty argument list, the class must define a no-arg constructor. If the newInstance() method cannot instantiate the class for any reason, it throws an InstantiationException. If the class or constructor is not accessible, it throws an IllegalAccessException.

Putting the tools to use

I stated earlier that this technique is easy to use. Now I intend to prove it. Here's all of the code necessary to dynamically load a class file into an executing application and create an instance of the class:

Object voila(String str) { Object o = null;

try { Object o = Class.forName(str).newInstance(); } catch (Exception e) { System.out.println(e); }

return o; }

The try..catch statement is necessary in order to catch any errors or exceptions that are raised during loading and instantiation.

Notice how the string is passed down. Any string is possible as long as it names a valid Java class. The string can be obtained in any number of ways: The user can input it, the program can read it from a configuration file, or the user can pass it in on the command line. In short, the program doesn't have to know its value at compilation time.

If you want to interact with the string, you'll probably want to cast it to an instance of a class of the appropriate type. The following code shows how:

Foo f = null;

if (o instanceof Foo) { f = (Foo)o; }

This last step illustrates one of the limitations of this technique: The new class must either be a subclass of a class known to the program at compile time, or it must implement an interface known to the program at compile time. Although it's possible to load any class (because all classes technically are subclasses of the Object class), it's not particularly useful; while classes can be dynamically specified, their methods cannot. This limitation is one of the reasons the Introspection API was introduced as part of Java 1.1.

How does it stack up?

I've shown you the technique for extending your applications as they execute. Now it's time for me to put my money where my mouth is and show you a functional demo. Luckily, I am prepared. Take a look at the applet below.

You need a Java-enabled browser to see this applet.

This applet is an implementation of a simple stack-based machine, which consists of a stack of items -- in this case, a collection of zero or more numbers stacked one on top of another, just like in the figure below.

A stack of numbers

There are only two valid operations you can perform on this stack of numbers. You can either push a number on the top of the stack or you can pop a number off the top of the stack. The figure below illustrates these operations.

Pushing onto and popping off of a stack

Take some time to peruse Stack.java, the source code for the stack. Stack.java is a subclass of Panel, and it displays the top five numbers in the stack.

The stack is embedded within a control panel. The control panel includes a field in which commands can be entered and a selector from which the previously entered command can be selected.

Believe it or not, this simple stack forms the basis of a very powerful calculating machine. All we need to complete the program are a few operators (add, subtract, and so on) to operate on the numbers in the stack. However, the operators can only operate on the numbers via the two operations mentioned earlier -- push and pop. Let me give you an example.

Consider an operator called add. This operator pops the top two numbers off the stack, adds them, and then pushes the result back on the stack. When the operation is complete, the stack will be one item shorter and the top two numbers will have been replaced by their sum.

Now add is only one of many different operators. I can think of several more off hand -- subtract, divide, factor, truncate, and convert to francs, to name a few. There are plenty more where those came from, but I wouldn't want to have to sit down and think of them all, much less code them.

Which brings me to my next point. Rather than trying to think up and code all possible operators, I'd like to build the framework and perhaps provide a few common operators. I'll leave the fancy operators -- such as converting to francs -- for you to develop and add into the program as you need them.

With that in mind, take a look at a method that accepts a string, locates the correct class, loads it, and invokes the proper method:

private void invoke(String str) throws Exception { Double d = null;

// parse the string

try { d = new Double(str); } catch (NumberFormatException nfexp) { ; }

// if the string is not a number, it must be // a operator... let's try it...

if (d == null) { _ch.addItem(str);

// load the class...

Class c = Class.forName("howto.operators." + str);

// create a new instance...

Object o = c.newInstance();

// cast it...

Operator op = (Operator)o;

// do it...

op.operate(_s); } else { _s.push(d.doubleValue()); } }

Pretty simple, isn't it? The complete code for the control panel is in Main.java.

Let me tell you a bit more about the applet before I conclude. You can push numbers on the stack by typing them in the text field. Once you've pushed a few numbers, try out the following operators: add, subtract, clone, pop, rotate, and swap. I bet you can you guess what they do.

Conclusion

Consider for a moment more what a powerful tool I've left you with. Even though I didn't write every conceivable operator, I've made it possible for you to add those you need, when you need them, without ever having to touch the original source code. That's not a convenience to be lightly dismissed. It's certainly something I'd like to see more of. Application developers, I hope you're listening.

Todd Sundsted has been writing programs since computers became available in convenient desktop models. Though originally interested in building distributed applications in C++, Todd moved to the Java programming language when it became the obvious choice for that sort of thing. Todd is co-author of the Java Language API SuperBible. In addition to writing, Todd is president of Etcee, which offers Java-centric training, mentoring, and consulting.

Learn more about this topic

  • Browse the documentation for class Class online http://www.javasoft.com/products/jdk/1.0.2/api/java.lang.Class.html
  • See how dynamic extensibility is handled in TCL http://sunscript.sun.com/man/tcl8.0/TclCmd/load.htm
  • See how dynamic extensibility is handled in PERL ftp://ftp.cis.ufl.edu/pub/perl/CPAN/doc/manual/html/pod/perlmod.html
  • See how dynamic extensibility is handled in Python http://www.python.org/doc/ext/node21.html#SECTION00400000000000000000
  • Download this article and the complete source code as a gzipped tar file http://www.javaworld.com/javaworld/jw-09-1997/howto/jw-09-howto.tar.gz
  • Download this article and the complete source code as a zip file http://www.javaworld.com/javaworld/jw-09-1997/howto/jw-09-howto.zip
  • Previous How-To Java articles

Join the discussion
Be the first to comment on this article. Our Commenting Policies