Before we talk about proxy objects in Java we should look at the pattern in general terms. Suppose you have a class that implements some interface, perhaps we call it Foo
. In this example we can just pretend for a moment that the code is external and that the concrete implementation is final or is otherwise difficult to extend for some reason.
Decorators
Some interface has methods defined:
public interface Foo<E> {
public E get(int index);
...
}
By principles of good object-oriented design we are programming to an interface instead of concrete implementations so that we decouple our code from whatever concrete implementation handles the details of some task.
public final class ConcreteFoo<E> implements Foo<E> {
// implementation details
}
Later on, we get a new requirement or, for the convenience of a blog post, decide that we would like to implement some additional functionality on top of Foo
instances. One approach to this design problem can be found in the Decorator pattern.
What is a "decorator"?
Decorator:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. [1]
So decorators are able to attach new features or behavior to an implementation dynamically at runtime. That sounds pretty cool. Setting it up is not too terrible either, less a little boilerplate.
public MoreFooDecorator<E> implements Foo<E> {
private Foo<E> someFoo;
public MoreFooDecorator(Foo<E> someFoo) {
this.someFoo = someFoo;
}
@Override
public E get(int i) {
performFooActionsBeforeAfterOrInsteadOfFooGet();
return someFoo.get(i);
}
// all the other methods of interface Foo
// ...
// maybe a factory method to help out with instantiation
}
Now we can decorate instances of Foo
with a kind of wrapper. When our production code would call foo.get(1)
it will now do something extra, or instead of just calling get
on the original instance.
... {
var extraFoo = new MoreFooDecorator(fooInstance);
// ...
// use extraFoo anywhere you would normally expect a Foo
}
That is the gist of using a decorator.
You take an instance of some type and decorate it with additional responsibilities at runtime. The decorator acts like a proxy for the instance, forwarding or augmenting requests sent to methods along the way.
Proxy objects in Java
Given the above we can see that Java supports mechanisms for decorating objects with new responsibilities, as described in the Decorator pattern [1]. What happens if the interface(s) you need to decorate an instance contain hundreds of methods in aggregate.
That could be a lot of boilerplate if you only need to augment a method or two!
public interface BloatedInterface {
void thisIs();
void theInterface();
void thatNeverEnds();
void somePoorDeveloper();
void startedCodingIt();
void notKnowingWhatItWas();
...
// pretend this goes on for quite some time
}
And then your decorator repeats all the things.
public BloatedInterfaceDecorator implements BloatedInterface {
private BloatedInterface instance;
@Override
public void thisIs() {
// that one method we want to change in some way
...
instance.thisIs();
}
// now define all the other methods too
// (╯°□°)╯︵ ┻━┻
...
Perhaps you are a framework developer and you can't predict ahead of time what interfaces you will need to implement opening up your library to some kind of Frankenstein's monster of programming headaches.
In addition to decorating instances, the above problems can also be solved with Java using dynamic proxies.
For this to work we need to augment the simple decorator pattern show above slightly. A small bit of extra boilerplate is required but in the end we won't need to implement potentially hundreds of methods of a given type.
The proxy expects to get a reference to an InvocationHandler
which will serve as the Decorator in this example.
Let us take List<E>
as an example and decorate instances at runtime with new responsibilities. In this example we will decorate instances of List<E>
such that we capture crude statistics and then forward all method calls to a List
instance.
class ListInvocationHandler<E> implements InvocationHandler {
private Map<String, Long> stats;
private List<E> list;
public ListInvocationHandler(List<E> list) {
this.list = list;
stats = new HashMap<>();
}
public Object invoke(Object proxy, Method method, Object[] args) {
// decorated to capture statistics of ALL method calls
Long count = stats.getOrDefault(method.getName(), 0L);
stats.put(method.getName(), count + 1);
// invoke method on the non-proxy instance
try {
return method.invoke(list, args);
} catch (Exception e) {
// handle exceptions accordingly
throw new RuntimeException(e);
}
}
public void inspect() {
// emit captured statistics to a database?
for (var k : stats.keySet()) {
System.out.println("Method: " + k + ", count: " + stats.get(k));
}
}
}
So that's pretty cool.
We decorated a List<E>
with an InvocationHandler
but that doesn't technically implement the same interface (yet) as collections that implement java.util.List
. So how can we pass this handler around in our code?
We need to create a proxy instance that implements the required interface. Suppose that some method in our code is expecting instances of type List<String>
.
private processList(List<String> listOfItems) {
for (var item : listOfItems) {
// do something with each item
}
}
The above method will generate a single call to our List
instance. That will be a call to get an Iterator
from the list. See also List<E>::iterator()
Here is how we can create a proxy instance that looks, acts, and feels like an instance of List
except that all calls to the interface are actually decorated with statistics gathering code before dispatching the method calls to a real instance.
...
var handler = new ListInvocationHandler<String>(someList);
...
List<String> myDecoratedList = (List<String>) Proxy.newProxyInstance(
List.class.getClassLoader(),
new Class<?>[] { List.class },
handler);
Now whenever we pass our list around the code we can pass myDecoratedList
and our code will be unaware that we are actually gathering statistics (as in the example above.) Of course, this is just an example use case.
Here is an example of output from calling inspect()
on the decorator (handler) defined above. In my example locally I added some extra println(...)
calls for good measure but you get the idea:
List<?> method call stats
----------------------------------------
Method: iterator, count: 1
Method: size, count: 100001
Method: get, count: 100000
From this log of stats we can see that somewhere in our code we have a for-each style loop (probably) due to the call to iterator()
and apparently we have a standard for-index style loop calling size()
and get(i)
on the list we passed in.
That is interesting!
On one hand, thanks to reflection, this is completely dynamic and we didn't have to implement tons of boilerplate to make the decorator work. On the other hand there are more edge cases and oddities that can arise when using the proxy approach described here.
It is not perfect but it is another tool for your toolbox.
You can learn more about using Proxy
and InvocationHandler
from the documentation here:
Happy decorating.
Thanks for reading and I will see you next time.
Cheers!
References:
[1] Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1995). Design patterns: Elements of reusable object-oriented software. Addison-Wesley.