Conversing with other systems using Proxies

· FrameworksLanguage

In my humble opinion, very few people know of the Proxy class, even though it has been supplied with the JDK ever since JDK 1.3. I myself only became aware of its existence when Tiger was beginning to emerge, and Proxy was the solution to the rmic hell where you had to manually generate your interfaces' proxies (AKA stubs). Starting with Tiger, a common Proxy (or rather, InvocationHandler) is supplied, removing the need for rmic entirely by generating these stubs at runtime.

How does it work?

The Proxy class is unique because it allows dynamic implementation of interfaces according to a given business logic. The Proxy class generated by the getProxyClass method implements all the interfaces passed as the second argument, which essentially means it will respond to instanceof calls and reflection calls the same as any other class implementing these interfaces. To implement the Proxy's business logic an InvocationHandler class is implemented which delegates the method invocations on the interface, and the equals, toString and hashCode methods on the Proxy itself. The delegation is done by any business logic written by the developer, usually delegated to a remote system or one that is out of the Java scope such as a database or proprietary system. Since the Proxy class instantiated from the class returned by the getProxyClass, or by calling the convenience method newProxyInstance, is implementing the interfaces provided, it can be safely cast back to any of these interface from the moment of creation.

Proxy Example

As stated before, proxies are very useful in accessing remote systems. This can be done either by connecting to a remote system via a certain protocol and then passing the information parsed from the Method and Object[] parameters passed to the InvocationHandler, by using meta data, or a combination of the two. For this example, I will use meta-data declared by the use of annotations to determine what needs to be done on the remote service. The example will pseudo-implement the newest JDBC 4.0 feature, of specifying a Select annotation above interface methods to create statements which will be sent to a Connection instance. For that I will assume already having a Select annotation which accepts an SQL statement as its value, and for simplicity the methods will return a ResultSet and not the new DataSet<T> class. As stated above, we do this by implementing our business logic into the InvocationHandler:
public class SQLInvocation implements InvocationHandler {
  public SQLInvocation(Connection conn) { this.conn = conn; }
  
  public Object invoke(Object proxy, Method method, Object[] params) throws Exception {
    Select selectAnnotation = method.getAnnotation(Select.class);
    if (selectAnnotation != null) {
      return conn.createStatement().executeQuery(selectAnnotation.value());
    } else if ("equals".equals(method.getName()) { 
      
      // obviously not the best check for the equals method, 
      // need to check that the parameters are the same as well
      
      // implementation will check equality between the two
      // internal Connection objects, checking first that the two
      // classes are the same type etc.
    } else if ("hashCode".equals(method.getName()) {
      return conn.hashCode(); 
    } else if ("toString".equals(method.getName()) {
      return "Invoker for " + conn.toString();
    } else {
      // handle errors, throw exception...?
    }
Note that the hashCode and toString are being checked for specifically and not just invoked using Method.invoke. This is to prevent the performance trouble caused by using reflection's invocation procedures. Also note that this is a simple implementation. After implementing this, we could now add code to support PreparedStatements by creating a PreparedStatement instance for every Method which has parameters in it. Then, the appropriate parameters could be set before invocation by the types received from the Method instance and the values of the Object[] array.

Other Possible Usages

The example above is somewhat not the common usage of a Proxy. It was merely used to demonstrate something familiar, and even to show off JDBC 4.0 a bit. Obviously, there are are other usages as well:
  • The main-stream usage of Proxy is to use it to remotely access objects or procedures. This can be done over standard protocols (usually already supplied for by the framework) or for proprietary protocols by parsing the information received from the Method and Object[] parameters passed to the invoke method.
  • Producing smarter proxies which use meta-data information to determine whether to cache the data retrieved from the remote object, and for how long. These proxies usually decorate an already implemented Proxy instance, and delegate the methods to it.

Misuses of Proxy

Like any other software component, Proxy instances can be misused or implemented badly. In my opinion, even some of the examples brought up in the proxy guide provided with JDK 1.3 are bad. Some examples of misuses:
  • Using the reflection invocation (Method.invoke) on local objects. A reason for this might be when wrapping a local object to provide AOP-like behavior to it. This is bad because of the performance cost of reflective invocations, only to provide something that is already provided by AOP frameworks such as AspectWerkz.
  • Not delegating Object methods, specifically hashCode, equals and toString This is bad for two reasons: First, it is required according to the documentation of Proxy. Second, it can cause odd results and remove the transparency remote objects provide locally. If retrieving the same remote object in two locations, an equals invocation should return true, as they point to the same object remotely, which is what the transparency of Proxy is all about.
  • In general, it is better to use already implemented frameworks or classes instead of trying to create your own Proxy. It might just be that some other framework is implementing the feature you're requesting in a more efficient way.

Some Conclusions

The Proxy class has one main purpose - to provide a simple, one time implementation to frameworks dealing with remote systems, specifically with remote objects. They can also be used as a bridge to another system which is not object-based (such as the database example), but these implementations might be better implemented using different means. Hope I managed to explain this a little bit unknown class here... I would like to hear of anyone who implemented the usage of Proxy in his project, and for what uses?