The Proxy class is one of the cooler features of the Java SDK. In short, it allows you to create a dynamic implementation of a statically defined interface. This is useful in cases where you wish to have type safety at the interface level, while still having a general-purpose implemention.
To use Proxy, you must give the Proxy class an interface to implement and an “InvocationHandler”. The InvocationHandler is a callback that you implement that provides the behavior for the class. The Proxy class dynamically generates an implementation of that interface that dispatches all method invocations to the “invoke” method of your InvocationHandler.
This has a number of applications such as RMI, Object-Relational mapping, AOP, etc. It’s also possible to create a full implementation of an interface where the behavior is determined by convention alone.
For example, one can create a BeanProxy that automatically implements a bean ( assuming only setters and getters ). As a client of BeanProxy, I can simply define at interface:
public interface ExampleInterface {
//…
public String getStringValue();
public void setStringValue(String value);
}
I can now create an instance of that interface and use it as follows:
ExampleInterface inter = BeanProxy.createBean(ExampleInterface.class);
inter.setStringValue("mystr");
System.out.println(inter.getStringValue()); //prints "mystr"
No implementation of the interface required!
Here’s how it works. The following is the BeanInvocationHandler ( see the attached BeanProxy for the full source code ):
private static class BeanInvocationHandler implements InvocationHandler {
//…
/**
* Backing store for property values
*/
private final Map propertyValues = new HashMap();
//…
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ( isGetter( method ) ) {
//the method is a getter--get the value of the property
Object rv = propertyValues.get(getPropertyName(method));
//…(special processing for primitive types)
return rv;
}
else if ( isSetter( method ) ) {
//the method is a setter--store the property in the map
propertyValues.put(getPropertyName(method), args[0]);
return null;
}
else {
throw new UnsupportedOperationException("Unable to handle method: "+method);
}
}
//…
}
When I make a call to inter.setStringValue(“mystr”), the following occur:
1)BeanInvocationHandler.invoke is called with a method parameter equal to the “setStringValue” method object and an args Object [] equal to
new Object[]{“mystr”}
2)The method is a setter ( isSetter returns true ), so we add an entry “StringValue=mystr” to the propertyValues HashMap.
When I then make a subsequent call to inter.getStringValue(), the following occur:
1)BeanInvocationHandler.invoke is called with a method parameter equal to the “getStringValue” method object.
2)The method is a getter ( isGetter returns true ), so we fetch “StringValue” from the propertyValues HashMap, which is equal to “mystr”.
3)The “invoke” method returns “mystr”.
4)“mystr” is returned from the call inter.getStringValue() and we display “mystr”.
One caveat: beware of performance implications of proxy. Every method invocation requires at a minimum the creation of the args Object[]. In addition, primitive typed arguments must be boxed for each method invocation!