The Artima Developer Community
Sponsored Link

Chapter 8 of Inside the Java Virtual Machine
The Linking Model
by Bill Venners

<<  Page 15 of 20  >>

Advertisement

Example: The Dynamic Extension of the Greet Application

As an example of an application that performs dynamic extension through user-defined class loaders, consider the following class:

// On CD-ROM in file linking/ex6/Greet.java
import com.artima.greeter.*;

public class Greet {

    // Arguments to this application:
    //     args[0] - path name of directory in which class files
    //               for greeters are stored
    //     args[1], args[2], ... - class names of greeters to load
    //               and invoke the greet() method on.
    //
    // All greeters must implement the com.artima.greeter.Greeter
    // interface.
    //
    static public void main(String[] args) {

        if (args.length <= 1) {
            System.out.println(
                "Enter base path and greeter class names as args.");
            return;
        }

        GreeterClassLoader gcl = new GreeterClassLoader(args[0]);

        for (int i = 1; i < args.length; ++i) {
            try {

                // Load the greeter specified on the command line
                Class c = gcl.loadClass(args[i]);

                // Instantiate it into a greeter object
                Object o = c.newInstance();

                // Cast the Object ref to the Greeter interface type
                // so greet() can be invoked on it
                Greeter greeter = (Greeter) o;

                // Greet the world in this greeter's special way
                greeter.greet();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

The Greet application is a fancy incarnation of the typical "Hello, world!" program. Greet uses a user-defined class loader to dynamically extend itself with classes--called "greeters"--that do the actual work of telling the world hello.

A greeter is any class that implements the com.artima.greeter.Greeter interface:

// On CD-ROM in file linking/ex6/com/artima/greeter/Greeter.java
package com.artima.greeter;

public interface Greeter {

    void greet();
}

As you can see from the code above, the Greeter interface declares only one method: greet(). When a greeter object's greet() method is invoked, the object should say hello to the world in its own unique way. Here are a few examples of greeters:

// On CD-ROM in file linking/ex6/greeters/Hello.java
import com.artima.greeter.Greeter;

public class Hello implements Greeter {

    public void greet() {
        System.out.println("Hello, world!");
    }
}

// On CD-ROM in file linking/ex6/greeters/Greetings.java
import com.artima.greeter.Greeter;

public class Greetings implements Greeter {

    public void greet() {
        System.out.println("Greetings, planet!");
    }
}

// On CD-ROM in file linking/ex6/greeters/Salutations.java
import com.artima.greeter.Greeter;

public class Salutations implements Greeter {

    public void greet() {
        System.out.println("Salutations, orb!");
    }
}

// On CD-ROM in file linking/ex6/greeters/HowDoYouDo.java
import com.artima.greeter.Greeter;

public class HowDoYouDo implements Greeter {

    public void greet() {
        System.out.println("How do you do, globe!");
    }
}
Greeters can be more complex than the above four examples. Here's an example of a greeter that chooses a greeting based on the time of day:
// On CD-ROM in file linking/ex6/greeters/HiTime.java
import com.artima.greeter.Greeter;
import java.util.Date;

public class HiTime implements Greeter {

    public void greet() {

        // Date's no-arg constructor initializes itself to the
        // current date and time
        Date date = new Date();
        int hours = date.getHours();

        // Some hours: midnight, 0; noon, 12; 11PM, 23;
        if (hours >= 4 && hours <= 11) {
            System.out.println("Good morning, world!");
        }
        else if (hours >= 12 && hours <= 16) {
            System.out.println("Good afternoon, world!");
        }
        else if (hours >= 17 && hours <= 21) {
            System.out.println("Good evening, world!");
        }
        else {
            System.out.println("Good night, world!");
        }
    }
}

The Greet application doesn't know at compile-time what greeter classes it will load and where those classes will be stored. At run-time it takes a directory path as its first command-line argument and greeter class names as subsequent arguments. It attempts to load the greeters using the path name as a base directory.

For example, imagine you invoke the Greet application with the following command line:

java Greet greeters Hello
In this command line, java is the name of the Java virtual machine executable. Greet is the class name of the Greet application. greeters is the name of a directory relative to the current directory in which the Greet application should look for greeters. Hello is the name of the greeter.

When the Greet application is invoked with the above command line, it attempts to load greeters/Hello.class and invoke Hello's greet() method. If the Hello.class file is indeed sitting in a directory named greeters, the application will print:

Hello, world!

The Greet application can handle more than one greeter. If you invoke it with the following command line:

java Greet greeters Hello Greetings Salutations HowDoYouDo
the Greet application will load each of the four greeters listed and invoke their greet() methods, yielding the following output:
Hello, world!
Greetings, planet!
Salutations, orb!
How do you do, globe!

The Greet application works by first checking to make sure there are at least two command-line arguments: a directory path and at least one greeter class name. It then instantiates a new GreeterClassLoader object, which will be responsible for loading the greeters. (The inner workings of class GreeterClassLoader, a subclass of java.lang.ClassLoader, will be described later in this section.) The constructor for GreeterClassLoader accepts a String that it uses as a directory path in which to look for greeters.

After it has created the GreeterClassLoader object, the Greet application invokes its loadClass() method for each greeter name that appears on the command line. When it invokes loadClass(), it passes the greeter class name, args[i], as the sole parameter:

// Load the greeter specified on the command line
Class c = gcl.loadClass(args[i]);
If the loadClass() method is unsuccessful, it throws an exception or error. If the loadClass() method is successful, it returns the Class instance for the newly loaded type.

Note that in addition to being loaded, the type requested of loadClass() may possibly be linked and initialized by the time loadClass() returns. If the type had been actively used prior to the loadClass() invocation that requested the type, that active use would have triggered its loading, linking, and initialization. Regardless, by the time the next statement, which calls newInstance() on a Class reference, is executed, the type will definitely have been initialized. If the type has not yet been initialized, calling newInstance() will trigger the initialization of the type (which must be a class), because a class must be initialized before an object of that class is instantiated. So if the type hadn't been initialized prior to the loadClass() invocation, calling newInstance() will trigger the initialization.

Once loadClass() has returned a Class instance, the Greet application's main() method instantiates a new instance of the greeter by calling newInstance() on the Class instance:

// Instantiate it into a greeter object
Object o = c.newInstance();
When the newInstance() method is invoked on a Class object, the virtual machine creates and initializes a new instance of the class represented by the Class object. To initialize the new instance, the virtual machine invokes its no-arg constructor. (Note that for this statement to work without throwing an exception, the newly loaded type must be a class, not an interface, must be accessible, must not be abstract, and must contain a no-arg constructor that is accessible.)

The Greet application then casts the Object reference that points to the greeter object to type Greeter:

// Cast the Object ref to the Greeter interface type
// so greet() can be invoked on it
Greeter greeter = (Greeter) o;

Finally, armed with a Greeter reference, the main() method invokes the greet() method on the greeter object:

// Greet the world in this greeter's special way
greeter.greet();

The Greet application demonstrates the flexibility inherent in Java's linking model. The Greet application does not know at compile time what greeters it will be loading and dynamically linking to at run-time. In the examples above, class Greet invokes the greet() method in classes Hello, Greetings, Salutations, and HowDoYouDo. But if you look at Greet's constant pool, there is no symbolic reference to any of these classes. There is only a symbolic reference to their shared superinterface, com.artima.greeter.Greeter. Greeters themselves, so long as they implement the com.artima.greeter.Greeter interface, can be anything and can be written and compiled anytime, even after the Greet application itself is compiled.

<<  Page 15 of 20  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use