Dynamic Decorating Proxy
As we saw earlier, we cannot create a dynamic proxy for each
Person instance if object creation times are critical.
Instead, in this pattern we will create a singleton dynamic proxy
for an interface that looks similar to a Person, but
where each method takes the target Person as the first
parameter. This interface is essentially a stateless version of the
Person interface:
/**
* Stateless equivalent of the Person interface.
*/
public interface StatelessPerson {
long getId(Person targetPerson);
void setId(Person targetPerson, long id);
String getName(Person targetPerson);
void setName(Person targetPerson, String name);
Collection getAddresses(Person targetPerson);
void setAddresses(Person targetPerson, Collection addresses);
boolean isModified(Person targetPerson);
void markModified(Person targetPerson);
}
Because every method accepts the target person to be operated
on, an implementation of this interface can be stateless and a
singleton. Given this, here is the singleton dynamic proxy for the
StatelessPerson that does the person-modification
logic we want using reflection:
/**
* Object Modification Advice for a StatelessPerson
*/
public class PersonModificationIh implements InvocationHandler{
public Object invoke(Proxy proxy, Method method, Object[] args)
throws Throwable {
// call the method the client wanted to call
Object returnObject = this.proceed(method, args);
// call markModified() for setXXX() method calls
if (method.getName().startsWith("set")){
Person targetPerson = (Person)args[0];
targetPerson.markModified();
}
return returnObject;
}
private static final Class statefulClass = Person.class;
/**
* Calls the stateful equivalent of the stateless method.
* This could be factored out to a base class or a reusable
* utility.
*/
private Object proceed(Method statelessMethod,
Object[] statelessArgs) throws Throwable {
// statefulObject is assumed to be the first arg
Object statefulObject = args[0];
// locate stateful method based on stateless one
Method statefulMethod=findStatefulMethod(statelessMethod);
// convert the stateless args to stateful args
Object[] statefulArgs=new Object[statelessArgs.length-1];
System.arraycopy(args, 1, statefulArgs, 0,
statefulArgs.length);
try{
// invoke the stateful method on the stateful object
return statefulMethod.invoke(statefulObject,
statefulArgs);
}catch(InvocationTargetException ite){
throw ite.getCause();
}
}
/**
* Given a stateless method on a stateless class, returns
* the stateful equivalent on the corresponding stateful
* class. This could be factored out to a base class
* or a reusable utility.
*/
private Method findStatefulMethod(Method statelessMethod){
// this could easily be cache'd to be more efficient
Class[] statelessParmTypes = statelessMethod.
getParameterTypes();
Class[] statefulParmTypes =
new Class[statelessParmTypes.length - 1];
System.arraycopy(statelessParmTypes, 1, statefulParmTypes,
0, statefulParmTypes.length);
Method statefulMethod = null;
try {
statefulMethod = statefulClass.getMethod(
statelessMethod.getName(), statefulParmTypes);
} catch (Exception e) {
throw new RuntimeException("Could not find stateful " +
" equivalent of stateless method: " + statelessMethod);
}
return statefulMethod;
}
}
Given this, we can create a Person decorator for
our original stateful Person that leverages the
stateless Person. Here's a portion of that class:
public class PersonDynamicProxy implements Person{
private StatelessPerson statelessPerson;
private Person targetPerson;
/**
* Construct a Person dynamic proxy where all method calls
* are delegated to the statelessPerson.
*
* @param targetPerson
* @param statelessPerson
*/
public PersonDynamicProxy(Person targetPerson,
StatelessPerson statelessPerson){
this.targetPerson = targetPerson;
this.statelessPerson = statelessPerson;
}
public long getId() {
return statelessPerson.getId(targetPerson);
}
public void setId(long id) {
statelessPerson.setId(targetPerson, id);
}
public String setName(String name) {
statelessPerson.setName(targetPerson, name);
}
Figure 1 below shows the high-level runtime
interaction we achieve by this design:

Figure 1. Dynamic proxy interaction
Before the interactions in Figure 1 occurred, the
client obtained a Person implementation. This
implementation was decorated by a PersonDynamicProxy
object which held a reference to 1) a PersonImpl
instance, targetPerson, and 2) a runtime generated
PersonModificationDynamicProxy dynamic proxy whose
invocation handler was PersonModificationIh. Let's now
look at the interactions shown in Figure 1:
The client calls the setName(String) method on its
Person implementation. The setName(String) method on
the PersonDynamicProxy is invoked.
PersonDynamicProxy, in turn, calls the
setName(Person, String) method on the
PersonModificationDynamicProxy, passing in the target
Person it should operate on. As a reminder, here's the
PersonDynamicProxy code that made the call to the
runtime generated dynamic proxy:
public String setName(String name) {
statelessPerson.setName(targetPerson, name);
}
The invoke() method is then invoked on the
PersonModificationIh, the invocation handler for the
runtime generated PersonModificationDynamicProxy.
Within the invoke() method, the equivalent method,
setName(String), is invoked on the
targetPerson using reflection.
PersonModificationIh then determines that the name
of the invoked method started with "set," so it also called the
markModified() method on targetPerson
using reflection.
With this approach, we now have an advisor that can advise every
setXXX() method and does not have to change if more
setXXX() methods are added to the Person
interface. Because we took a decorating approach for both the
static and dynamic proxy, we can mix and match our static proxies
with our dynamic proxies in any order we want. Additionally, all of
the frequent object creation is done with the Java new
keyword, so object creation times are not sacrificed. Here is some
example code that creates a person with both the static
address lazy-loading advice and the dynamic person-modification
advice:
/**
* Creates a Person with two advisors: the static lazy-loading advisor
* and the dynamic person-modification advisor.
*/
public Person createPerson() {
Person person = new PersonImpl();
person = new PersonDynamicProxy(person,
statelessModificationAdvice);
person = new AddressLazyLoadingProxy(person);
return person;
}
/**
* Stateless Object Modification advice
*/
private static StatelessPerson statelessModificationAdvice=null;
static{
// initialize the stateless person-modification advice singleton
ClassLoader cl = PersonFactoryImpl.class.getClassLoader();
InvocationHandler ih = new PersonModificationIh();
Class[] interfacesToProxy = new Class[]{StatelessPerson.class};
statelessModificationAdvice = (StatelessPerson)Proxy.
newProxyInstance(cl, interfacesToProxy, ih);
}
The drawbacks to this stateless dynamic proxy approach are the
following:
- A stateless interface must be maintained along with the
stateful interface. I imagine some build-time code generation could
be used to keep the two interfaces synchronized. For now, all I've
done is written a JUnit test (see the sample code in the Resources section) that looks at both class
definitions and fails if they are out of sync.
- A dynamic proxy along with reflection is used at runtime so
method calls will be slower than direct method calls. I imagine a
CGLIB code-generated proxy could be used for the stateless advisor
if you find that the method calls through the dynamic proxy are too
slow for your needs.
Conclusion
Advising objects to separate concerns is an extremely powerful
pattern. Spring-like approaches to AOP are fantastic choices if few
instances of the advised class need to be created. However, many
domain objects do not fall in this category. AspectJ is a
full-featured AOP implementation that is designed to advise both
singletons and domain objects. However, AspectJ is not a trivial
technology to bring into a development environment, so knowing
alternatives is a good thing. For advising domain objects, we
explored object pooling, class extension, and using the Decorator
pattern, and there are undoubtedly others. Depending on the
scenario, each approach enables a separation of concerns that
object-oriented programming and aspect-oriented programming
advocate. What I attempted to illustrate in this article were some
factors that go into choosing an advice approach and some options
available for advising domain objects.
Resources