Java Tip 71: Use dynamic messaging in Java
Get around Java’s inability to trap a request it doesn’t know about — by implementing the ‘doesNotUnderstand’ method!
Programmers who have worked in Smalltalk or Objective C are used to the doesNotUnderstand
message, which enables an object to trap a request it doesn’t know about. This can’t happen in Java directly, because all messages are statically type-checked at compile time — so it’s never possible to send a message directly to an object that Java doesn’t understand.
However, with the introduction of Java 1.1, Sun released the <a href="
API. It allows an object to inspect itself to find out what methods it supports, and to dynamically invoke them. The API elevates Java from being a dynamic runtime environment to being a truly dynamic language.
This Java Tip shows you how to use the Reflection API, and specifically how to use it to implement a simple system for sending dynamic messages. In our particular case, this simple system is the doesNotUnderstand
message.
In order to understand how to implement dynamic messaging, it is necessary to first examine the java.lang.Reflection
API.
Reflection
In the world of programming, reflection is a process that allows an object to look at itself (as in a mirror, ergo the term) to see what it can do. Reflection identifies all of the members that are associated with an object (field members and method members) and makes it possible for that object to interact with them.
One question that is asked often is, What’s the difference between introspection and reflection? The answer: Reflection allows you to find out which members an object has; and introspection allows you to identify which (JavaBean) properties an object has. That’s why the java.lang.reflect
lives in a separate package, and the Introspector
object is in the java.beans
package. Links to these packages on Sun’s Java site, as well as further information on JavaBeans, are available in the Resources section below.
The java.lang.reflect
package defines wrapper objects for manipulating arrays, methods, and fields. However, you can’t create objects like <a href="https://java.sun.com/products/jdk/1.1/docs/api/java.lang.reflect.Method.html">Method</a>
, <a href="https://java.sun.com/products/jdk/1.1/docs/api/java.lang.reflect.Field.html">Field</a>
, or <a href="
directly; you have to invoke a factory method in the <a href="
object (<a href="https://java.sun.com/products/jdk/1.1/docs/api/java.lang.Class.html#getMethod">getMethod</a>
, <a href="https://java.sun.com/products/jdk/1.1/docs/api/java.lang.Class.html#getConstructor">getConstructor</a>
, and so on).
Find out what an object can do
To give you an example of reflection at work, this sample piece of code, <a href="https://images.techhive.com/downloads/idge/imported/article/jvw/1999/04/displaymembers.java">DisplayMembers.java</a>
, takes a Java classname as its argument, and then prints out all the members it contains.
The next piece of code, listed below, takes a fully qualified classname as its argument(s), and for each class listed prints out all of the methods that are declared in that class. For instance, running:
java DisplayMembers DisplayMembers
lists the main
and displayClassInfo
methods along with their types and exceptions. (I have been purposefully lazy here by declaring that the main
method generates an exception; this saves having to type in a try/catch
block, but results in ugly code. Don’t use this style unless you want to write compact demo code.)
Do what I say, not what I do
Once you’ve got a list of the methods an object has, you can invoke them dynamically. This allows you to select a method dynamically (based on its name and argument types) and invoke it against a given object. The next example demonstrates how an object can be instantiated and given a method name, and how it can take optional arguments.
This is implemented in the source code <a href="https://images.techhive.com/downloads/idge/imported/article/jvw/1999/04/dwis.java">DWIS.java</a>
. This class can be run as an application, and it takes a class name, method name, and a value. The method is then resolved using getMethodCalled
. Once the method has been located, it is invoked using meth.invoke
, and the result is displayed on System.out
.
When DWIS
is run, the output looks like this:
java DWIS java.lang.Integer toHexString 123
> 7b
java DWIS java.lang.Integer toBinaryString 45
> 101101
java DWIS java.awt.Button setLabel "Hello, world!"
> java.awt.Button[button0,0,0,0x0,invalid,label=Hello, world!]
java DWIS java.lang.System getProperties
This piece of code works in four stages;
-
The class name given is resolved to a
java.lang.Class
usingClass.forName
-
The first method with the same number of arguments is located using
getMethod
-
The arguments are converted from strings into objects
- The method is invoked and displays the result and/or the target
The convertFromString
method is used to convert the command-line argument (a string) into an object of the appropriate type.
When the convertFromString
method is called, the desired argument type is passed as the class
parameter. If a boolean method (such as setEnabled
) has been resolved, then the class parameter will be Boolean.TYPE
.
There are some standard types that can be easily converted; boolean, character, and integer values can be parsed from a string, since suitable constructors are available. These types are tested at the top of the method.
If the type is not located, then the convertFromString
method tries to instantiate an object of type class
with a string constructor.
This convertFromString
method can be extended to deal with other argument types, such as Float
, Double
, and other user-defined types. Creating these extensions is left as an exercise for the reader.
It’s time to build the ‘doesNotUnderstand’ system
Having looked at how reflection can be used to dynamically resolve a method that takes a specific number of arguments, we are now ready to implement our doesNotUnderstand
system. We need to implement a message delegator that calls a method with a given name and set of arguments. If this fails (for example, the method isn’t present), we can invoke another predefined method.
<a href="
essentially is a tidied-up version of the DWIS
object, with a few nicer features. For one thing, the method lookup (in getMethod
) carries out some sensible checks to see whether the arguments that are passed are compatible with the given arguments.
Note that in the event multiple methods may be called — such as a parseFrom
(object) and parseFrom
(string) — this getMethod
will select the “nearest” method, not necessarily the correct one.
The code above allows us to invoke dynamic methods, based on a method name and a sequence of arguments. Although this is much easier than having to work out the Method
objects yourself, it does not achieve the objective: the doesNotUnderstand
message.
Fortunately, adding the functionality for the doesNotUnderstand
message is relatively simple. All we have to do is detect when a given method cannot be found; that is, we need to catch when a NoSuchMethodException
is raised.
We can therefore modify the original code:
Method meth = getMethod(tclass,message,args,target == null);
to become
try {
Method meth = getMethod(tclass,message,args,target == null);
} catch (NoSuchMethodException e) {
// send it to another object
if (message.equals("doesNotUnderstand"))) {
throw e;
} else {
Object newArgs[] = { message, args };
sendMessage(target,"doesNotUnderstand",newArgs);
}
}
and voilà! The code to call the doesNotUnderstand
method is implemented. When a method is invoked using sendMessage
and it cannot be found (a NoSuchMethodException
is thrown), the above code sends the doesNotUnderstand
method to the same target.
To use the MessageSender
, we need to implement the doesNotUnderstand
method in our target:
public class TestObject {
public void doesNotUnderstand(String messageName, Object args[]) {
// this will be called when a message is not understood
System.out.println("Method " + messageName + " is not understood");
}
// other methods go here
}
Now, when we call an unknown method using MessageSender.sendMessage
, our special doesNotUnderstand
method will be invoked. In this example, this special method only prints out a message, but it could be used to implement a proxy or other such design patterns.
TestObject to = new TestObject();
MessageSender.sendMessage(to,"helloWorld",null);
// prints out "Method helloWorld is not understood"
The applet below demonstrates in action the technique described in this tip. A method name can be typed in the bottom field of the applet, and clicking on Invoke will call that method with no arguments on itself (FinalMessageApplet
). List methods will generate a list of all the zero-argument methods that the object supports.
If you type a nonexistent method name into the applet field, the doesNotUnderstand
method is called, displaying the method name and arguments. Click on the links here for the source to the example applet and the final message sender.
Note: In order to view the applet below, your browser must support JDK 1.1.
Conclusion
Using the Reflection API may be cumbersome, but it is a powerful way to implement dynamic messaging in Java. There is a certain amount of bookwork involved in obtaining the necessary Method
objects, but creating a sendMessage
method is relatively straightforward — and the best part is that you can let that method do all the hard work for you.
Once the sendMessage
has been implemented, changing it to invoke doesNotUnderstand
when a method is not recognizable is a simple task. Plus, the MessageSender
object can be reused generically to invoke dynamic messages on any object with any argument types.