Java may be a compiled language like C and C++, but it also lets you do things at run time undreamed of by C programmers.
The Java programming language provides an interesting mechanism which allows a Java program to dynamically load and inspect any given Java class. The collection of classes and methods supporting this mechanism is known as the Java Core Reflection API, or simply the Reflection API.
The classes that comprise the Reflection API are as follows:
- java.lang.Class This class is used to represent class constructs.
- java.lang.reflect.Constructor A special class for inspecting any of a class's constructor methods.
- java.lang.reflect.Method This class provides an abstract means of invoking a Java method from a given class or object.
- java.lang.reflect.Modifier The Modifier class is used to determine whether special access modifiers (such as private, public, or protected) have been applied to a method or attribute.
- java.lang.reflect.Array This class is used as an abstract representation of array objects.
- java.lang.reflect.Field This class is used to represent an attribute field.
In working with the JDBC (Java Database Connectivity) API I became curious to understand the APIs used to dynamically load JDBC drivers. I decided to investigate the Core Reflection API after I'd seen some seemingly arcane Java JDBC code.
In this article I introduce the Core Reflection API by using a single class to assist in constructing a dynamically loadable driver. The second software project I present, a primitive scripting language, involves more of the supporting classes.
A Note about the Coding Style
I have purposely avoided implementing try/catch blocks in various places throughout the code. Alternatively, I've just implemented a throws clause in each method definition. I have also opted not to use the JavaDoc commenting standard prior to each method.
My goal in coding the Java programs in this style is to enhance their clarity, by keeping the actual coding density to a minimum. I have also assumed that all example classes are in the same logical package. Thus, I've developed them all in the same directory.
A Dynamic Driver Project
The dynamic driver will simply provide a mechanism to convey an error message to a user. A given application will be able to determine which error message driver to use based on a configuration file entry.
As a starting point, I define an abstract base class for the driver called ErrorBase. This Java component defines one abstract method called display:
public abstract class ErrorBase { public abstract void display(String s); }Next, I define two subclasses of ErrorBase. The first subclass, ConError, displays the given error message to the character-based console output screen.
public class ConError extends ErrorBase { public void display(String s) { System.out.println("Error: " + s ); } }The second subclass, GUIError, displays the error message in a GUI message dialog.
import javax.swing.*; public class GUIError extends ErrorBase { public void display(String s) { JOptionPane.showMessageDialog(null, "Error: " + s); } }The first sample program that uses the two classes is called ErrTest.java (Listing 1). It simply instantiates objects of each class type and invokes the display method from each. The behavior of these objects is determined at compile time. When this program is invoked, the message "Error: This is a console message." should appear on the console screen. A message dialog should also appear containing the text "Error: This is a GUI message.".
Consider the program DynTest.java (Listing 2). This program reads a property file to determine which kind of error message it should display. Granted, this functionality could be accomplished with a specified property code and a switch-case construct in the main program, but for the sake of illustration this program dynamically loads the appropriate class based on input. In fact, newer subclasses that derive from ErrorBase can be constructed and utilized in DynError.java without ever having to recompile the program again!
The first section of DynTest.java loads the properties file "DynTest.prop" from the current directory. The contents of the properties file are as follows:
ErrorDriver=ConError # ErrorDriver=GUIErrorA pound symbol (#) starting a line represents a comment. The name of the error message driver (in this case, ConError) is then retrieved into String variable s via the get method.
The expression Class.forname(s).newInstance() actually instantiates the object. It first dynamically loads the class specified in s by calling the static method Class.forname. The object returned from the Class.forName call is actually an object of type Class. Once that object is returned, the newInstance method is called to invoke the specified ErrorBase-derived object.
Assuming that no exceptions are thrown, the ErrorBase reference variable e can be used to invoke the display method. To alter the behavior of the program, uncomment the active option of the properties file DynTest.prop and comment out the alternate option using a pound symbol.
As new methods of displaying errors become necessary, the programmer can simply write a new class as a subclass of ErrorBase and include the driver name in the DynTest.prop file.
The above example demonstrates the luxury of defining the class architecture in the base class ErrorBase early in the design process. After dynamically loading the class and instantiating an object of that class type, the virtual display method is bound dynamically to reference variable e.
This technique lends itself to the "plug-in" metaphor enjoyed by many software packages today. By leveraging this technique, programmers could define plug-in architectures for their own applications, allowing for extensions by users.
Methods and Fields
The prior example demonstrated a test program that exhibited dynamic behavior as defined by an ErrorBase object. The goal of the next example will to leverage the Core Reflection API to implement a simple scripting system. While a full scripting system could utilize the Reflection API to manipulate and control all facets of a given Java program, this scripting system will be used to provide dynamically selected debugging information. Before I attempt to implement the scripting system as a whole, I provide a look at a few other classes from the Core Reflection API: Class, Method, and Field.
Class provides a means of examining and utilizing Java class features dynamically. Method is a class that represents a Java class method or a Java object method. After a Method object is instantiated, a program can then invoke its counterpart Java method dynamically. Field is a class that represents a member attribute of a given Java class or object. A program can utilize Field objects to dynamically read or change member attribute values.
The program ReflectTest.java (Listing 3) illustrates a technique of dynamically invoking Java methods using the Method class. ReflectTest first instantiates an object of type ReflectTest in method main and invokes the go method. A Class object is then obtained via the getClass method.
The next step in the process is to obtain a Method object representing the method we want to call. The first method to be invoked is called test as a matter of fact, so is the second. The test method is overloaded. The two methods can be distinguished only by their parameter signatures.
The first of the two test methods accepts no parameters. To obtain a Method object representing this method, the program calls the Class object's getMethod method, passing it a String containing the method name and a null value for the array of parameter descriptions. Passing null as the second parameter indicates that the method to be invoked accepts no parameters.
After instantiating this Method object, the program can call the method it represents via its invoke method. invoke accepts two parameters: the object reference (or null if the method happened to be static) and an array of parameter objects.
Since this method accepts no parameters, the program passes null as the second parameter to the invoke method. At this time, the test() method is dynamically invoked, causing the phrase "Hello, world!" to appear on the console screen.
To invoke the second test method, the program must tell getMethod to find a version of test that accepts a String object and an int scalar. When a program uses any of the Java primitive data types, it uses a special constant defined in that type's counterpart wrapper class for the information needed in getMethod.
The second parameter to getMethod is an anonymous array of Class objects. The first array element is a Class object representing type String. The second Class object is a constant representing the int scalar type.
To call the method via the Method object just created, the program must now supply a list of objects as the second parameter to the invoke method.
Again the program creates an anonymous array to contain the parameters. The first object in the array is a String object; the second is an Integer object, which is appropriately translated to the scalar int type when the method is called. The final segment of ReflectTest inspects the value of the member attribute testVar using a Field object.
The Field object is first instantiated via the Class object's getField method. The program passes the field by name to getClass. Once the program has an instantiated field object, it retrieves testVar's value by calling getInt. The Field class provides methods for retrieving values from each of the scalar data types and the String object. For each of these "get" methods, a counterpart "set" method is provided as a means to alter the given attribute.
A Simple Scripting System
Using the Method and Field classes, I can now implement a new class called Script (Listing 4), which provides a simple debugging scripting mechanism.
The scripts that the Script object reads will be standard Java property files. The scripting engine will be based on programmer-defined hook names. Each line of script code associated with a particular hook will appear with the hook name followed by a period followed by a sequential number beginning with 1.
The Script class supports six script commands, as follows:
- print string-literal Displays a string-literal and newline.
- printVar varname Displays a variable.
- callMethod methodName Invokes a method.
- setInt varName value Sets int variable to literal value.
- setString varName value Sets String object to literal value.
- setBoolean varName value Sets boolean variable to literal value.
To demonstrate the scripting class, I have created a simple game that requires the user to guess a random number in the range of 1 to 100 inclusive. If you examine the source code to Guess.java (Listing 5), you can see that early in the program's code, a Script object is instantiated. The first argument to the constructor for the Script object is the reference to the current Guess class. This reference is used in all commands that refer to Field objects or Method objects.
Note also that there are several calls to doHook peppered throughout the code in Guess.java. These calls are places where the programmer has determined that scripting might be important in debugging a problem.
If you invoke Guess without the property file present, it behaves just as you might expect all calls to doHook return immediately. The program randomly determines a number and repeatedly allows the user to guess the number. After users have been successful in guessing the number, they are asked to start a new game.
If you invoke Guess with the file scr.prop (Figure 1) in the same directory, it behaves differently. When the doHook method is called with an argument of "AfterInit", the AfterInit section of the scr.prop is interpreted and executed.
The first AfterInit script line simply displays a console message indicating that initialization has been completed. The second AfterInit script line changes the title String object, which is displayed to the user in each dialog box. The title change is done so that users understand they are executing in a debugging mode. The third AfterInit script line changes the formerly random number to 27. Suppose the user had noted problematic behavior when the generated random number value was 27. Using this script, she can force the number to a fixed value for debugging purposes.
The next hook is invoked after the user has entered her guess. The UserInit section of the property file is then interpreted. The two lines of code in the UserInit section simply indicate what number the user had guessed.
The final hook invoked is the PlayAgain section. In this case, repeated play is not desired, so the script engine invokes the Guess object's terminate method. This closes the application without asking the user to continue playing.
While the above example may not be entirely useful in a real-world situation, it does show how to dynamically convey variable values. This feature alone could be very beneficial in analyzing peculiar program behavior. In addition, the scripting example shows how to modify attributes of a given class to forcibly control the program flow. Again, this feature could be beneficial when trying to debug a problem. Finally, the scripting engine provides the ability to invoke methods from the Guess object itself.
A fully developed script language might give a user the ability to dynamically assemble the program's complete behavior from a set of exposed methods and attributes.
Closing
As I have demonstrated, the Java Core Reflection API can be a useful tool in providing dynamically changeable behavior to statically compiled code.
Jim Lawless is a senior software architect for a major midwestern financial institution. He welcomes your correspondence at jimbo@radiks.net.