Design with dynamic extension

How dynamic extension works in Java and how to use it in your designs

At the Second Annual JavaOne Developer Conference in 1997, I met a programmer from Mexico named Gerardo Horvilleur. Gerardo had created a product that he was publicizing from a booth on the convention floor. His product interested me because in those days I was working on my Java virtual machine book, and his product was one of the few I'd seen that seized new design opportunities made possible by Java's architecture.

Gerardo's product, a Java library called Montage, enabled Java applets or applications to display a special kind of image. The image was special because it was defined not just by data, but also by code.

To display one of these images, the Montage library would first show a background image (stored as plain old image data), then it would dynamically load and execute some Java code (called painters) that massaged the background image. To massage the background image, a painter could process the image to produce a special effect or animate the image -- anything that could be done to an image with Java code a painter could do. The result was the Montage image.

This product stoked my inner nerd sufficiently enough that I wanted to know the details of how he did it. I arranged a meeting with Gerardo in the Hacker's Lounge, a social gathering spot at JavaOne.

There he described the inner workings of the class loader he used to load painters. Our conversation drifted to class loaders, in general, and to Java's overall architecture. Gerardo was very enthusiastic about Java and excited in particular about the new opportunities made possible by its architecture.

Eventually, Gerardo and I left the Hacker's Lounge and headed for a JavaOne session. As we made our way through the crowd, he continued his animated discussion of the benefits of Java.

One thing he said that stuck in my mind was: "When you look at C++'s linking model, it is really just C's linking model with mangled names tacked on. If you look at C's linking model, it is really just Fortran's linking model. But when you look at Java's linking model, it doesn't look like any of those. That's the significant difference about Java!", he exclaimed. "It's the linking model!"

In this article, I'll discuss how to take advantage of one of the more interesting design opportunities made possible by Java's linking model: dynamic extension. As a background to the design guidelines, I'll give an overview of Java's linking model, touching on topics such as name spaces, forName(), and class loaders.

For the full details on these topics, I'll refer you to Chapter 7 of my book Inside the Java Virtual Machine, which, partly in honor of my conversation with Gerardo, I named "The Linking Model." In this chapter, I explain in painstaking detail the process of dynamic linking in the JVM, and show how to write and use class loaders.

But you needn't rush to the bookstore to read this chapter; I have excerpted it in its entirety on my Web site. (See the Resources section for a link to this material.)

Dynamic extension

Java's architecture enables you to write programs that dynamically extend themselves at runtime. Java programs can dynamically extend themselves by choosing at runtime classes and interfaces to load and use.

Looked at another way, dynamic extension means that at compile time, you don't necessarily need to know about all the classes and interfaces your program will use at runtime. In fact, some of those classes and interfaces may not even exist when you do your compile.

Java has two ways to do dynamic extension: forName(), and class loaders. forName(), a static method in java.lang.Class, is the simple, straightforward way to do dynamic extension. Class loaders, subclasses of java.lang.ClassLoader, are the more complicated (and more powerful) way to do dynamic extension.

In a Java program that uses dynamic extension, irrespective of whether it uses forName() or class loaders or both, names of types (classes and interfaces) will be passed around in the program as Strings. To request a certain class be loaded, the program will pass as a String the fully qualified name of the desired type to forName() or the class loader. Because the type name is handed to forname() or the class loader as a String at runtime, the program can be written such that the actual contents of the Strings (the names of the types your program will load at runtime via dynamic extension) are not known at compile time.

Class loaders

Whether you knew it at the time, if you've run a Java program, you have already been using class loaders. Every type (class or interface) used by a Java program must be loaded into the Java virtual machine (JVM), and the JVM loads types through class loaders.

Java has two kinds of class loaders -- the primordial class loader and class loader objects. The primordial class loader is sometimes called the system class loader or the default class loader. Class loader objects are sometimes called custom class loaders.

The difference between these two kinds of class loaders is important to understand. Whereas the primordial class loader is part of the JVM implementation, class loader objects are part of the running Java application.

For example, when I downloaded the JDK for Windows 95, I got a JVM. This JVM, to the best of my knowledge, is written primarily in C. In my case, the primordial class loader is a part of the C program that defines my JVM.

Armed with this installation of Java on my computer, I can then write and run a Java application that uses class loader objects. In this case, I would define class loaders in Java, which my Java application would instantiate and use.

So the difference is this: The primordial class loader (there is only one of these per JVM implementation) is designed and written by the creators of each JVM. Class loader objects are designed and written by Java programmers. Class loader objects are defined as classes (subclasses of java.lang.ClassLoader) and instantiated into regular Java objects on the heap.

Whenever the JVM loads a class or interface, it will use either the primordial class loader or a class loader object. Because it is part of the JVM implementation, one (and only one) primordial class loader is always available to a running application. Class loader objects, by contrast, behave like objects. At any one time during an application's lifetime, the application may have zero to many class loader objects in existence and in use.

Name spaces

When you start a Java application, you give the name of a class that has a main() method to a JVM. The JVM starts executing Java at the indicated main() method, and the application is off and running.

Each application lives inside its very own "instance" of the JVM, with memory areas that are private just to that application. Although different threads of the same application can all access a common heap, for example, threads in one Java application can't directly access the heap in another Java application. Each Java application gets its own Java virtual machine.

Inside a JVM, each class loader (be it primordial or object) gets its own name space. A name space is simply the set of fully qualified names of all the classes and interfaces that a class loader has loaded so far. A fully qualified name of a class is the name of its package, plus a dot, plus its simple name. For example, the fully qualified name of the Hashtable class from the java.util package is java.util.Hashtable.

Within a single name space, all fully qualified names are unique. Two different name spaces, however, can contain identical fully qualified names. This architecture enables a single Java application to load and use two (or more) different classes that have the same fully qualified name. So long as those like-named classes are loaded by different class loaders, which will cause the classes to be placed into different name spaces, the Java application will be able to load and use them.

As an example, Figure 1 shows a snapshot of two name spaces in a single Java application. Two fully qualified names are common to both name spaces: java.lang.Object and Mouse. The upper name space contains two other fully qualified names (Animal and Cat) that don't appear in the lower name space. Likewise, the lower name space contains two fully qualified names (Device and Keyboard) that don't appear in the upper name space. Within each name space, however, all names are unique.

Figure 1. Two name spaces

All of this is fine and good, but begs the question: How are the names in a name space related to the actual types being named? When a JVM loads a type (class or interface), it parses and extracts information about the type from a class file. It places this information (in some data structure devised by the people who implement the JVM) into a logical area of the JVM's memory called the method area. So when the JVM loads a class, a chunk of type data is added to the method area and a fully qualified name is inserted into a name space. Every fully qualified name in every name space is associated with a chunk of data in the method area that defines that named type.

For example, Figure 2 shows the relationship between names (Mouse and java.lang.Object) in the name spaces of the previous example and type data in the method area. One fully qualified name that both name spaces contain is java.lang.Object. In the case of this name, both entries (one in each name space) are associated with the same chunk of type data in the method area.

By contrast, although both name spaces also contain the fully qualified name Mouse, each entry (one in each name space) is associated with a different chunk of type data in the method area. In the upper name space, the name Mouse is associated with a chunk of type data that describes a small, furry animal with a pink nose. In the lower name space, the name Mouse is associated with a chunk of type data that describes a device you attach to your computer.

Figure 2. Names and definitions

Dynamic linking and name spaces

When you compile a Java program, you get one class file for each type (class or interface) you define in source code. The class file is an intermediate compiled binary format for the type. Class files, which haven't been linked, contain symbolic references to other types in a list called the constant pool. At runtime, the JVM dynamically links the Java application by resolving the symbolic references contained in the constant pools of class files. This process is called constant pool resolution.

Name spaces in the JVM arise from a simple rule that all JVMs must follow when they resolve the symbolic references contained inside class files. Sometimes, an entry in a constant pool may refer symbolically to a type that hasn't yet been loaded. When such entries are resolved, the JVM must load the referred-to type. Because all types must be loaded by a class loader, the JVM must at that point decide which class loader to ask to load the type.

To choose a class loader, the JVM uses this simple rule:

The Resolution Rule - The JVM loads referenced types via the same class loader that loaded the referencing type.

An example with no dynamic extension

This one fundamental "Resolution Rule" of Java's linking model is what generates name spaces inside a JVM. If a class named Cat, for example, contains a symbolic reference to a class named Mouse, the JVM will load Mouse via the same class loader that loaded Cat. Because Mouse is loaded via the same class loader that loaded Cat, Mouse will end up in the same name space as Cat.

Thus, given these classes...

// In file dynaext/ex1/Cat.java
public class Cat {
    public static void main(String[] args) {
        Rodent myToy = new Mouse();
        myToy.scurry();
    }
}
// In file dynaext/ex1/Rodent.java
public class Rodent {
    public void scurry() {
        System.out.println("Rodent scurrying");
    }
}
// In file dynaext/ex1/Mouse.java
public class Mouse extends Rodent {
    public void scurry() {
        System.out.println("Mouse scurrying");
    }
}

the name spaces of the Cat application (just after scurry() is invoked on the Rodentreference) will look as shown in Figure 3. Because no class loader object is instantiated in this application, only one name space (that of the primordial class loader) exists. Because the primordial loader loaded Cat, the JVM will use the primordial loader to load Mouse.

Figure 3. The rule in action

Dynamic extension with forName()

Class java.lang.Class contains a static method, forName(), that enables you to dynamically load types into your program. For example, the following application, named Cat, dynamically loads classes that descend from Rodent, makes an instance, and invokes scurry() on the resulting object.

// In file dynaext/ex2/Cat.java

public class Cat { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class c = Class.forName(args[0]); Rodent myToy = (Rodent) c.newInstance(); myToy.scurry(); }

}

// In file dynaext/ex2/Rodent.java

public class Rodent { public void scurry() { System.out.println("Rodent scurrying"); }

}

// In file dynaext/ex2/Mouse.java

public class Mouse extends Rodent { public void scurry() { System.out.println("Mouse scurrying"); }

}

Related:
1 2 3 Page 1
Page 1 of 3