Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Using java.lang.reflect.Proxy to Interpose on Java Class Methods

 
 

Articles Index

Class Methods

Library interposition has proven to be a very useful technique in C language programming. With this approach, it is not necessary to recompile any existing source code--or even have access to the source code--in order to trace, profile or debug function calls. The goal of this technical article is to explore a technique for interposing on Java methods.

Solaris Library Interposition

The following example uses interposition to record the frequency of malloc, calloc and realloc system calls for various amounts of memory, and displays the results in a histogram. A shared library called malloc_hist.so redefines the functions: malloc, calloc, realloc as follows:

   void * malloc(size_t size)
    {
        static void * (*func)();
        void * ret;

        if(!func) {
            func = (void *(*)()) 
            dlsym(RTLD_NEXT, "malloc");
        }

        ret = func(size);

        bump_counter(&mdata, size);
  
        return(ret);
    }

    void * calloc(size_t nelem, size_t elsize)
    {
        static void * (*func)();
        void * ret;
        int i;

        if(!func)
            func = (void *(*)()) 
            dlsym(RTLD_NEXT, "calloc");

        ret = func(nelem, elsize);

        for(i=0;i<nelem;i++)
            bump_counter(&cdata, elsize);
  
        return(ret);
    }

    void * realloc(void *ptr, size_t size)
    {
        static void * (*func)();
        void * ret;

        if(!func)
            func = (void *(*)()) 
            dlsym(RTLD_NEXT, "realloc");

        ret = func(ptr, size);
        bump_counter(&rdata, size);
  
        return(ret);
    }


The redefined exit function collects the memory allocation information stored in some data structures and prints it to a file.

Setting the LD_PRELOAD environment variable to (some path)/malloc_hist.so ensures that the user-defined functions are loaded first. After some pre-processing, the system malloc functions are loaded (if necessary) and called. For more information about LD_PRELOAD and runtime linking, see the Linkers and Libraries Guide.

Java Method Interposition

An equivalent technique could be very useful for developers in tracing, profiling and debugging Java programs. Proxy class methods could be interposed in order to examine arguments, record the frequency of calls, timing information, and so forth, before or after calling the original method. This technique is not quite as simple for methods, however, since Java is a pure object-oriented language which does not allow any stand-alone (global) functions. Methods are not loaded individually, as C functions are, but rather are loaded as part of the class within which they are defined. Therefore, any override must start at the class level rather than at the method level. The CLASSPATH environment variable plays a role here similar to what LD_LIBRARY_PATH does for C programs. It provides an ordered search path for the classloader to find a needed classfile.

At class load time, the interposed class must be found before the original one. This can be readily accomplished through manipulation of the CLASSPATH environment variable or a -classpath switch on the commandline. The interposed class must have the same full name as the original. For example, a class Foo may exist in package pub.foo and create an instance of class Bar from package pub.bar. Creating a file Bar.java in another directory, such as fake/pub/bar and prepending /fake to the classpath, ensures that the JVM will load our definition of class pub.bar.Bar rather than the original. In this way, we can interpose our definitions of Bar's methods over the originals.

There is a problem, however, if we now wish to dispatch to the original method. Since each classloader maintains a cache of unique class names, the interposed and original classes pub.bar.Bar cannot coexist in the same classloader's namespace. Therfore, the original class must be loaded by a separate classloader (using a different classpath) and a technique must be devised to "trampoline" from the interposed method call to the original.

Proxy Class Interposition

Before tackling those problems, let's look at a built-in Java language facility that can be used to simulate much of the functionality provided by C language interposition. J2SE v1.3 has introduced a dynamic proxy class in the Java Reflection package. A dynamic proxy class implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Proxy classes and instances are created using static methods of the java.lang.reflect.Proxy class. The Proxy.getProxyClass method returns the java.lang.Class object for a proxy class given a class loader and an array of interfaces. The proxy class will be defined in the specified class loader and will implement all of the supplied interfaces. Each proxy class has one public constructor that takes one argument, an implementation of the java.lang.reflect.InvocationHandler interface. Rather than having to use the reflection API to access the public constructor, a proxy instance can be also be created by calling the Proxy.newProxyInstance method, which combines the actions of calling Proxy.getProxyClass and invoking the constructor with an invocation handler.

For example:

 public class TraceProxy 
 implements java.lang.reflect.InvocationHandler {

 public static Object newInstance(Object obj) {
     return java.lang.reflect.Proxy.newProxyInstance(
             obj.getClass().getClassLoader(),
             obj.getClass().getInterfaces(),
             new TraceProxy(obj));
        }
    ...
  }


Once a new proxy instance has been created, it is legal to cast it to any one of the interfaces that it implements, and to call any method supported by that interface.

For example:

    Bar b = (Bar) 
    TraceProxy.newInstance(new BarImpl());
    b.hello();

Method invocations on an instance of a dynamic proxy class are dispatched to the invoke method in the instance's invocation handler, and they are encoded with a java.lang.reflect.Method object identifying the method that was invoked and an array of type Object containing the arguments. Arguments of primitive types are wrapped in an instance of the appropriate primitive wrapper class, such as java.lang.Integer or java.lang.Boolean. The implementation of the invoke method is free to modify the contents of this array.

With this facility, we may create our own proxy class which can intercept a method call, do some pre-processing, invoke the original method, then do some post-processing. This is exactly the method interposing behavior that we seek!

For example:

    public Object invoke(Object proxy, 
    Method m, Object[] args) throws Throwable
    {
        Object result;
        try {
            System.out.print("begin method " +
             m.getName() + "(");
            for(int i=0; i<args.length; i++) {
               if(i>0) System.out.print(",");
                  System.out.print(" " +
                   args[i].toString());
            }
            System.out.println(" )");
            result = m.invoke(obj, args);
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
           throw new RuntimeException
           ("unexpected invocation exception: " +
                                    e.getMessage());
        } finally {
            System.out.println("end method " +
             m.getName());
        }
        return result;
    }

Following is the complete code for a simple example of method interposing using a dynamic proxy class.

Foo.java

    package pub.foo;

    import pub.bar.*;
    import pub.proxy.*;

    public class Foo {
    
        public static void main(String[] args) {
            Bar bar = (Bar) 
            TraceProxy.newInstance(new BarImpl());
            bar.hello(2001, "xxx");
            bar.goodbye("yyy", 2002);
        }
}

Bar.java

    package pub.bar;

    import java.io.*;

    public interface Bar {
        public void hello(int i, String s);
        public void goodbye(String s, int i);
    }

BarImpl.java

    package pub.bar;

    import java.io.*;

    public class BarImpl implements Bar {
        public void hello(int i, String s) {
            System.out.println
            ("   in pub.bar.Bar.hello");
        }

        public void goodbye(String str, int i) {
            System.out.println
            ("   in pub.bar.Bar.goodbye");
        }
    }

TraceProxy.java

package pub.proxy;

import java.lang.reflect.*;
import pub.bar.*;

public class TraceProxy 
implements java.lang.reflect.InvocationHandler {

  private Object obj;

  public static Object newInstance(Object obj) {
     return java.lang.reflect.Proxy.newProxyInstance(
             obj.getClass().getClassLoader(),
             obj.getClass().getInterfaces(),
             new TraceProxy(obj));
    }

    private TraceProxy(Object obj) {
          this.obj = obj;
    }

    public Object invoke(Object proxy, 
    Method m, Object[] args) throws Throwable
    {
        Object result;
        try {
            System.out.print("begin method "
             + m.getName() + "(");
            for(int i=0; i<args.length; i++) {
                if(i>0) System.out.print(",");
                    System.out.print(" " +
                    args[i].toString());
         }
         System.out.println(" )");
             result = m.invoke(obj, args);
         } catch (InvocationTargetException e) {
             throw e.getTargetException();
         } catch (Exception e) {
           throw new RuntimeException
           ("unexpected invocation exception: " +
                                  e.getMessage());
         } finally {
             System.out.println(
             "end method " + m.getName());
         }
         return result;
     }
}

Conclusion

The proxy class is relatively flexible and straightforward to use, but there are some distinct limitations on its use for method interposing. One limitation is that the method must be called through an instance of the proxy class. So nested methods calls, for instance, would not be intercepted. Another limitation is that the method must have been defined in an Interface that is implemented by the object being proxied. It can not be called through an instance of a class that does not implement an interface. The next article in this series will illustrate some techniques for overcoming these limitations.

About the Author

Tom Harpin works in Market Development Engineering at Sun's Burlington, MA campus. He works primarily on Distributed System Management vendor integration efforts with WBEM, and JMX. Prior to Sun, Tom helped design and implement several client-server systems for Delta Airlines.

For More Information

Linkers and Libraries Guide

Proxy Class Documentation
(Scroll down to java.lang.reflect in the top left box, click, then click on Proxy in the bottom left box)


Have a question about programming? Use Java Online Support.