| home / internet / reflection / 1 | [previous] |
|
|
The implementation part of George's solution happens in the createProxy
method. The static factory method createProxy wraps its argument
in a proxy that performs tracing. First, createProxy examines its
argument object for the direct interfaces that its class implements. It sends
that array of interfaces to Proxy.newProxyInstance, which constructs
a proxy class for those interfaces.1 Next, a TracingIH is constructed
with the argument as its target. Finally, createProxy constructs
and returns a new proxy that forwards its calls to the TracingIH.
This proxy implements all of the interfaces of the target object and is assignmentcompatible
with those types.
The delegation part of George's solution happens in the invoke method. The
invoke method in listing 4.3 first records the method name to a
java.io.PrintWriter. A more complete facility would also include
the arguments, but we omit them for brevity. Then the invoke method
forwards the call to the target and, subsequently, stores the return value.
If an exception is thrown, the exception is recorded with the print writer;
otherwise, the return value is recorded. Finally, the result of the call is
returned.
When a proxied method is called on a proxy instance, control first passes to
the invoke method with the following arguments:
• proxy: The proxy instance on which the method was invoked.
TracingIH happens to make no use of this parameter.
• method:A Method object for the invoked method.
• args: An array of objects containing the values of the arguments
passed in the method invocation on the proxy instance. args is
null if the method takes no arguments. Arguments of primitive
types are wrapped in instances of the appropriate primitive wrapper class;
for example, java.lang.Integer wraps an int.
getInterfaces method returns only the direct interfaces
of a class. As George has written the invocation handler, only methods declared
by direct interfaces are traced. In chapter 8, we present a method, Mopex.getAllInterfaces,
that finds all of the interfaces implemented by a class. What about methods that
are not implemented in an interface? George might be asked to supply a tool that
finds those methods and puts them in an interface. Reflection can help here, too,
but you will have to wait until chapter 7 to read how.
The declared return type of invoke is Object. The value returned
by invoke is subject to the following rules:
• If the called method has declared the return type void, the value returned
by invoke does not matter. Returning null is the
simplest option.
• If the declared return type of the interface method is a primitive type,
the value returned by invoke must be an instance of the corresponding primitive
wrapper class. Returning null in this case causes a NullPointer- Exception
to be thrown.
• If the value returned by invoke is not compatible with the interface method's
declared return type, a ClassCastException is thrown by the method
invocation on the proxy instance.
The exception UndeclaredThrowableException may be thrown by the
execution of the invoke method. UndeclaredThrowableException
wraps non-runtime exceptions that are not declared by the interface for the
method being called. The cause of the wrapped exception may be accessed with
getCause. This wrapping of an exception may seem odd, but it is
necessary when you consider the difficulty of programming invocation handlers
that are limited to throwing just those exceptions known at the origin of the
call.
To fully understand the class TracingIH in listing 4.3, it is
best to understand how a using application is changed by the execution of the
statement
Dog proxyForRover = (Dog) TracingIH.createProxy( rover );
where Dog is a Java interface and rover contains
an instance of a class DogImpl that implements that interface.
Note that the proxy facility ensures that the proxy instance returned by createProxy
can be cast to Dog. Figure 4.3 presents a diagram that shows all
of the objects and classes that are relevant to the previous line of code. The
objects created by that line of code are in the gray area.
This invocation handler in listing 4.3 provides the module that George wants.
Instead of having to change source code, he can wrap objects with proxies and
have the users of the objects reference the proxies. This technique avoids all
of the shortcomings of the process George would have to follow without Proxy.
As mentioned earlier, the tracing invocation handler of listing 4.3 is missing
a test to turn tracing on and off dynamically. Instead, the application uses
either traced or nontraced versions of its classes. This is accomplished by
applying the Abstract Factory pattern for construction of the potentially traced
objects. That is, a class is declared that contains a method for creating new
instances of Dog. This method chooses whether to create instances of the Dog
class that traces or instances of the one that does not trace. An example factory
for implementations of the Dog interface is shown in listing 4.4.
Listing 4.4 A factory that chooses between traced and untraced versions of a class
Notice that the factory method newInstance is enhanced reflectively by using the
class object to create a new instance the same way as the factory method in the
previous chapter. The lines
if (traceIsOn) {
d = (Dog) TracingIH.createProxy(d, new PrintWriter(System.out));
}
assure that each Dog is wrapped in a tracing proxy when required. This puts the
tests for tracing at construction time rather than during execution of the methods
of Dog.
The factory method also conforms to design recommendations presented in
section 3.4.2. The newInstance method constructs instances using the new-
Instance method of Class. After construction, the new Dog is made ready for use
with a call to initialize.
| home / internet / reflection / 1 | [previous] |
Created: March 27, 2003
Revised: November 12, 2004
URL: http://webreference.com/internet/reflection/1