Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Design with dynamic extension

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

  • Print
  • Feedback

Page 7 of 7

Another way to interact with a dynamically loaded type is to use reflection to find interesting methods in the object, and to invoke these methods. This approach is taken by bean builder tools.

When you drop a bean into a bean builder, the bean builder dynamically extends itself with the classes of the bean. As the bean builder has no idea what kind of bean is coming, it uses reflection to find methods that adhere to JavaBeans style rules, and uses those methods to manipulate the bean.

Jumping out of the current name space

So, how does an application ever get out of the primordial name space? It does this by creating a class loader object and asking that class loader object to load the type.

To ask a class loader to load a class, simply invoke loadClass() on the class loader object, thereby passing in the fully qualified name of the type to load. The loadClass() method basically performs the same task as forName().

But loadClass() is rebellious -- it doesn't follow the Resolution Rule. Instead of using the same class loader to load the requested type as it loaded the class containing the code that invoked loadClass(), it uses itself to load the class. (After all, it is a class loader.) Thus, it loads the requested type into its own name space.

Here's an example of a Cat application that uses a class loader object to load a Rodent:

// In file dynaext/ex3/Cat.java
public class Cat {
    public static void main(String[] args)
        throws ClassNotFoundException, IllegalAccessException,
               InstantiationException {
        RodentClassLoader rcl = new RodentClassLoader();
        Class c = rcl.loadClass(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");
    }
}
This Cat application first instantiates RodentClassLoader (shown below). The resulting object is a class loader object because class RodentClassLoader extends class java.lang.ClassLoader. Cat invokes loadClass() on this class loader object, passing it the first argument to the application. Assuming the first argument is "Mouse", loadClass() will attempt to load Mouse into its name space. If it is successful, loadClass() will return a reference to the Class instance that represents the newly loaded type. After that, this Cat application looks the same as the previous Cat that used forName(). But the name spaces look different. Figure 5 shows what the name spaces of this Cat application look like after the execution of the scurry() method. The application has two name spaces because it has two class loaders -- the primordial class loader and the class loader object it instantiated, RodentClassLoader. Although every other class was loaded by the primordial loader and is, therefore, in its name space, class Mouse was loaded by the class loader object, so it sits in the class loader object's name space.

Figure 5. Two name spaces

About the author

Bill Venners has been writing software professionally for 12 years. Based in Silicon Valley, he provides software consulting and training services under the name Artima Software Company. Over the years he has developed software for the consumer electronics, education, semiconductor, and life insurance industries. He has programmed in many languages on many platforms: assembly language on various microprocessors, C on Unix, C++ on Windows, Java on the Web. He is author of the book: Inside the Java Virtual Machine, published by McGraw-Hill.

Read more about Core Java in JavaWorld's Core Java section.

Writing a class loader

Now, the reason you would usually write a class loader in the first place is to customize the loading process. The purpose of any class loader is (given a fully qualified name) to produce a binary stream of data (usually in the Java class file format) that represents that type and to import that type into the JVM. So the designers of each JVM implementation get to decide how their primordial class loader is going to produce the binary stream of data given the fully qualified name. The JVM that I received in the JDK for Windows 95 looks down the directories and zip files listed in an environment variable called the CLASSPATH. The primordial loaders of other JVMs can do it differently. Often, the reason you write a class loader is because you want to load classes in some other way than the default way provided by the primordial loader. You may, for example, want to download class files across a network. This is one reason why the class files for applets are loaded through class loader objects. You may want to decipher encrypted class files as they are read in. You may want to extract binary class definitions out of some proprietary database. You may want to load class files that end with the extension .purple instead of .class Or, you may even want to generate class definitions on the fly, so that your Java application can continually make itself smarter. Because you define class loaders in Java code, you are free to do anything to produce a binary stream of data in the Java class file format given a fully qualified name. (Well, at least you are free to write and compile the class loader. But in some runtime environments, the SecurityManager may prevent class loaders from being instantiated. Untrusted applets, for example, can't create class loader objects.) Class RodentClassLoader, shown below, loads classes in a special subdirectory of the current directory named hole. This class loader is a simple example of another potential reason for writing a class loader: to load class files not located in the class path.
// In file RodentClassLoader.java
import java.io.*;
import java.util.Hashtable;
public class RodentClassLoader extends ClassLoader {
    public synchronized Class loadClass(String typeName,
        boolean resolveIt) throws ClassNotFoundException {
        // See if type as already been loaded by
        // this class loader
        Class result = findLoadedClass(typeName);
        if (result != null) {
            // Return an already-loaded class
            return result;
        }
        // Check with the primordial class loader
        try {
            result = super.findSystemClass(typeName);
            // Return a system class
            return result;
        }
        catch (ClassNotFoundException e) {
        }
        // Don't attempt to load a system file except
        // through the primordial class loader
        if (typeName.startsWith("java.")) {
            throw new ClassNotFoundException();
        }
        // Try to load it from subdirectory hole.
        byte typeData[] = getTypeFromHole(typeName);
        if (typeData == null) {
            throw new ClassNotFoundException();
        }
        // Parse it
        result = defineClass(typeName, typeData, 0,
            typeData.length);
        if (result == null) {
            throw new ClassFormatError();
        }
        if (resolveIt) {
            resolveClass(result);
        }
        // Return class from hole
        return result;
    }
    private byte[] getTypeFromHole(String typeName) {
        FileInputStream fis;
        String fileName = "hole" + File.separatorChar +
            typeName.replace('.', File.separatorChar)
            + ".class";
        try {
            fis = new FileInputStream(fileName);
        }
        catch (FileNotFoundException e) {
            return null;
        }
        BufferedInputStream bis =
            new BufferedInputStream(fis);
        ByteArrayOutputStream out =
            new ByteArrayOutputStream();
        try {
            int c = bis.read();
            while (c != -1) {
                out.write(c);
                c = bis.read();
            }
        }
        catch (IOException e) {
            return null;
        }
        return out.toByteArray();
    }
}
I'm not going to delve into the nitty gritty details of how to write a class loader here. For that you can refer to Chapter 7 of Inside the Java Virtual Machine, which, as I mentioned above, is excerpted on my Web site. (See Resources for a link to the chapter.) Be sure to read the section entitled "Example: The Dynamic Extension of the Greet Application."

Design guidelines

Now that you've seen how Java's linking model supports dynamic extension, you may be wondering how you should put this cool architecture to use in your designs. The main design insight that I have for dynamic extension (which, while not that profound, is quite useful) is that dynamic extension facilitates customization. You can think of the chunks of Java code that your application loads dynamically as "plug-ins" or "personality modules." Perhaps you would like users to be able to plug their own algorithms into your product, to customize it to their needs and preferences. You can also design your product such that users can define a class that encapsulates the custom algorithm and then load it into your product dynamically. (This is the strategy pattern from the "Gang of Four" book, by the way, with dynamic extension added.) Or you can customize your own product for individual customers in the same way. Applets, for example, are a dynamic extension of a Java application fired off by a Web browser. They enable applet programmers to customize the browser. Servlets are dynamic extensions of a Web server that enable servlet programmers to customize the Web server. Resources, which are dynamically loaded based on the current locale, are customizations of internationalized Java programs that enable translators to localize the Java programs. Interacting with dynamically loaded types
In general, you should interact with dynamically loaded types by instantiating them and invoking methods declared in a superclass or superinterface about which you know. This, for example, is how browsers interact with applets. Every applet you write must descend from a superclass that the browser knows about, java.applet.Applet. The browser dynamically loads and instantiates your applet and casts the resulting reference to type java.applet.Applet. It then invokes init(), start(), and stop() on your applet, methods which are declared in java.applet.Applet. The other way to interact with dynamically loaded types (by discovering interesting methods via reflection) is reasonable only if you have absolutely no knowledge of what's coming. If you are writing a bean builder, you'll need to do this. Otherwise, you'll usually write your program such that it expects, as the dynamic extension, some subclass of a known class or some class that implements a known interface. forName() versus class loader objects
Once you have decided to use dynamic extension, how do you choose between forName() and class loader objects? In general, you should try to use forName() if possible, because forName() is simpler to use and understand. You should use class loader objects if:
  1. you need name spaces;
  2. you need to customize the load process; or
  3. you need to dynamically replace existing customizations in a live Java application.
Using name spaces
Why would you need name spaces? There are two reasons. Firstly, if your application may encounter name conflicts among the types it loads dynamically, you can use class loader objects to load types with potentially conflicting names into different name spaces. Browsers, for example, usually load applets from different sources into different name spaces. This enables a browser user to visit two different pages containing two applets written by two different people, both of whom may have given the same name to one of the classes that make up their applet. The second reason you may need name spaces is security. Java's linking model lets you shield types loaded into different name spaces from each other. You can design a system of dynamic extension in which types loaded into one name space can't even see types loaded into other name spaces. This capability is one aspect of Java's built-in security model. Security is the other reason (aside from avoiding potential name conflicts) that browsers usually load applets from different sources into different name spaces. It keeps untrusted applets from fooling around with trusted applets loaded from another source. However, this kind of security is optional, as the programmer who creates an application that dynamically extends itself via class loaders can decide to shield code in different name spaces from each other. It is also possible (at the programmer's option) to enable types loaded into one name space to get at and interact with types loaded into other name spaces.
  • Customizing the load process -- Another reason you might use class loaders is if you really need to customize the loading process. If you want to download class files across a network, for example, and the primordial loader doesn't know how, you'll need to write a class loader that can do it. This is another reason browsers use class loaders to load applets -- the primordial loader of the JVM used by the browser doesn't know how to download class files across the network.
  • Live replacement -- The third reason you might need a class loader is if you want to swap out a customization as an application is running. In other words, class loaders will enable you to dynamically extend your application with a class named Mouse, and later, to throw away that Mouse and dynamically extend the application with another class also named Mouse. You can't do this with forName() because once a type is loaded into a name space, it can't be removed unless the whole name space is removed. With a class loader, you can throw away a particular class loader object by dropping out all references to it, to any instances of any classes it loaded, and to any Class instances for the types it loaded. When you drop all these references, the class loader object and all the classes it loaded become unreferenced and available for garbage collection by the JVM. You can then create a new class loader object with a fresh (and empty) name space, and load the revised versions of the classes you just threw out with the old name space. This is how Web servers (running 24 hours a day, 7 days a week) enable servlet programmers to replace a servlet with a new version of the same servlet.

Next month

In next month's Design Techniques article, I'll talk about designing with type information.

A request for reader participation

I encourage your comments, criticisms, suggestions, flames (all kinds of feedback) about the material presented in this column. If you disagree with something, or have something to add, please let me know. You can either participate in a discussion forum devoted to this material, enter a comment via the form at the bottom of this article, or e-mail me directly, using the link provided in my bio below.

  • Print
  • Feedback

Resources
  • Bill's next book is Flexible Java http://www.artima.com/flexiblejava/index.html
  • An complete online reprint of Chapter 7, "The Linking Model," of Bill's book Inside the Java Virtual Machine http://www.artima.com/insidejvm/linkmod.html
  • The handout and slides for Bill's "Dynamic Extension in Java" talk http://www.artima.com/javaseminars/modules/DynaExt/index.html
  • Bill recently returned from his European bike trip. Read about it at http://www.artima.com/bv/travel/bike98/index.html
  • The discussion forum devoted to the material presented in this article http://www.artima.com/flexiblejava/fjf/dynaext/index.html
  • Links to all previous Design Techniques columns http://www.artima.com/designtechniques/index.html
  • Recommended books on Java design http://www.artima.com/designtechniques/booklist.html
  • A transcript of an e-mail debate between Bill Venners, Mark Johnson (JavaWorld's JavaBeans columnist), and Mark Balbe on whether or not all objects should be made into beans http://www.artima.com/flexiblejava/comments/beandebate.html
  • Object orientation FAQ http://www.cyberdyne-object-sys.com/oofaq/
  • 7237 Links on Object Orientation http://www.rhein-neckar.de/~cetus/software.html
  • The Object-Oriented Page http://www.well.com/user/ritchie/oo.html
  • Collection of information on OO approach http://arkhp1.kek.jp:80/managers/computing/activities/OO_CollectInfor/OO_CollectInfo.html
  • Design Patterns Home Page http://hillside.net/patterns/patterns.html
  • A Comparison of OOA and OOD Methods http://www.iconcomp.com/papers/comp/comp_1.html
  • "Object-Oriented Analysis and Design MethodsA Comparative Review" http://wwwis.cs.utwente.nl:8080/dmrg/OODOC/oodoc/oo.html
  • Patterns discussion FAQ http://gee.cs.oswego.edu/dl/pd-FAQ/pd-FAQ.html
  • Patterns in Java AWT http://mordor.cs.hut.fi/tik-76.278/group6/awtpat.html
  • Software Technology's Design Patterns Page http://www.sw-technologies.com/dpattern/
  • Previous Design Techniques columns http://www.javaworld.com/topicalindex/jw-ti-techniques.html