The Reflection API provides a class called Proxy, which we can use to create an interface between the user and an object. By having such a proxy, we can intercept calls to the object’s methods. The following example demonstrates a use of such functionality.
We are writing classes to represent a collection of books. We decide to add tracing property to our class so that every time a function is called, the information regarding calls and returns can be recorded. Having the tracing property for our classes, allows us to debug efficiently.
We have already written the interface and called it Book, and we have written a class (CSEBook) to represent Computer Science and Engineering books. We use these classes in the solution.
We could use if-statements throughout our programs to print out method name and any other properties we might be interested in, when tracing is turned on. However, editing previously written code to implement this property would be cumbersome task. Instead we would like to deploy a layer of code on top of our current code to implement any tracing properties we would be interested in. The Reflection solution we present does that.
To create a Proxy object we need to have an invocation handler, which handles method calls for a proxy. The invocation handler class is the class where we decide what to do with each method call. We have created an invocation handler through our Trace class by implementing the interface InvocationHandler. The Trace class creates a proxy instance for a given object by calling the Proxy.newProxyInstance method. A class loader object is passed to newProxyInstance to dynamically create a proxy class for the object. Furthermore, a list of interfaces is passed because the proxy instance created intercepts all the methods of the interface implemented by object’s class. Finally, an invocation handler object is passed so that the proxy instance can handle method calls. It takes object in as an argument in its constructor so that we have the object for which this proxy is being created.
The Trace class implements InvocationHandler’s invoke method. A proxy instance forwards its calls to the invoke method. The invoke method can then control the call to the object’s methods. In our case, we print out calling and returning information. Furthermore, notice that invoke received Method and args objects. Both of these are used to call the object’s method, where the arguments are represented by args. We already have the object reference in target. The call to object’s methods is then made by using Reflection.
The BookFactory class is the primary class we use to create book objects that allow tracing to be turned on or off. The constructor for BookFactory takes in two parameters: String name – representing the name of the class of which we wish to create an object of; boolean trace- turns trace on or off. In the constructor, we use the forName function to create a Class object of class represented by name.
BookFactory’s newInstance function takes in arguments needed to create a book. It then creates a book object using the Class object we created in the constructor. It initializes the book with the initial values. We could have done this by finding the appropriate constructor in the Class object and then get a new instance. However, that would have lead to more code in BookFactory. If trace is turned on, we then create a proxy instance of book object.
Let’s now go through the code in the BookFactory’s main method to understand what exactly happens. The BookFactory fac = new BookFactory("CSEBook",true) statement creates a BookFactory object which creates a Class object for CSEBook and sets trace to true. Book b = fac.newInstance(title, author, isbn, price) statement then creates a proxied instance of CSEBook. When functions like b.getTitle are called, the invoke method of Trace object which was created for the proxy is called with Method object for getTitle passed to it. The invoke method prints out some information and calls the CSEBook’s getTitle method. If however trace was set to false, then no proxied instance exists so the methods of CSEBook are directly called.
We wanted to add tracing to our classes and there were many different ways to implement that. We already discussed the method with the if-statements, however, another approach is to create a trace and non-trace version of each class. This presents a maintenance problem. Suppose we had p number of properties (i.e. trace, synchronized) that we wished our n number of classes to have. This would require us to have n2p classes. However, using Reflection’s Proxy we can achieve the same by writing only n+p classes. Thus, we have fewer classes to maintain. The benefits of using Proxy are therefore quite evident.
Source Code (BookFactory.java)
Source Code (Book.java)
Source Code (Trace.java)
Source Code (CSEBook.java)