WeakReferences and Dynamic Proxies
Posted by emcmanus on July 21, 2005 at 06:45 AM | Comments (2)
Yesterday I talked about how you can use
WeakReference to allow a resource to be
garbage collected even if it is referenced by another object,
a JMX MBean in the case in question. In fact, you can use
dynamic proxies to provide a more general
framework for this sort of situation.
In the specific situation I was discussing before, you have a
resource such as a cache, that may be garbage collected
when it is no longer referenced. And you have a
controller such as a JMX MBean that references the
resource. But you don't want this reference to stop the resource
from being garbage collected.
We can generalize this as follows:
- The resource is defined by some Java interface,
Cache in our example. Yesterday Cache
was a concrete class, but for the technique we're talking about
here it must be an interface, typically with a concrete class
like CacheImpl that provides its
implementation.
- Some references to the resource are weak, meaning
that they should not prevent the resource from being garbage
collected.
- If the resource is garbage collected, and these weak
references try to use it, we want to be able to define what
happens. This might just be to throw an exception, or it might
be take an action such as unregistering an MBean.
Dynamic proxies are an enormously useful feature of the
Java platform once you understand what they are for. Suppose
you have a Java interface, like Cache. A dynamic
proxy allows you to produce a Java object that implements that
interface. Every time someone calls one of the interface's methods
on this object, the following method from the
InvocationHandler interface is called to handle it:
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable;
The method parameter allows you to know which
method of the interface was called, and the args
parameter allows you to know what arguments it was called with.
(The proxy argument is usually ignored.)
Inside invoke, you can do whatever's appropriate.
You can create a handler that always throws an exception, to
produce a dummy implementation of an interface that checks error
behaviour for example. You can create a handler that logs the
method call or performs security checks before reinvoking the
same Method on another object.
It is this second kind of possibility that interests us here.
If the WeakReference is still valid, then we just
want to invoke the same method on the resource. If it's not
valid, then we want to invoke the user-supplied
missingHandler instead.
Suppose the Cache interface looks like this:
public interface Cache {
public int dropOldest(int n);
}
Suppose we create a dynamic proxy for this interface, like
this:
InvocationHandler handler = new SomeInvocationHandler(...);
Cache proxy = (Cache) Proxy.newProxyInstance(<...stuff...>, handler);
Then when we later call proxy.dropOldest(5), this will
result in a call to
handler.invoke(proxy, method, new Object[] {5});
where method is a
java.lang.reflect.Method representing the method
Cache.dropOldest(int).
What we want handler.invoke to do is to call this
same Method on the resource, assuming it still
exists, and otherwise run the user-supplied
missingHandler.
Putting this all together, we get this:
import java.lang.reflect.*;
import java.lang.ref.*;
import java.util.concurrent.Callable;
public class WeakProxy {
public static <T> T make(T resource,
Class<T> interfaceClass,
Callable<Object> missingHandler) {
Handler handler = new Handler<T>(resource, missingHandler);
Object proxy = Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[] {interfaceClass},
handler);
return interfaceClass.cast(proxy);
}
private static class Handler<T> implements InvocationHandler {
Handler(T resource, Callable<Object> missingHandler) {
this.resourceRef = new WeakReference<T>(resource);
this.missingHandler = missingHandler;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
T resource = resourceRef.get();
if (resource == null)
return missingHandler.call();
else
return method.invoke(resource, args);
}
private final WeakReference<T> resourceRef;
private final Callable<Object> missingHandler;
}
}
The type parameter <T> to WeakProxy.make
allows us to specify that the resource parameter
must implement the interface specified by the
interfaceClass parameter, and that the result of
WeakProxy.make also implements this interface.
In our example of a CacheControlMBean, we could
use this class as follows:
...
Cache cache = new CacheImpl(); // or whatever
Cache weakCache =
WeakProxy.make(cache, Cache.class, exceptionWhenMissingHandler);
CacheControlMBean cc = new CacheControl(weakCache);
ObjectName on = ...;
mbeanServer.registerMBean(cc, on);
...
private static final Callable<Object> exceptionWhenMissingHandler =
new Callable<Object>() {
public Object call() {
throw new IllegalStateException("Object no longer exists");
}
};
...
With some more work, we could have the WeakProxy
class create a thread that calls the missingHandler
as soon as the resource is garbage-collected, rather than waiting
for somebody to call the proxy. The code would look something
like the final version of the CacheControl MBean I
posted before.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
I'll find few more examples about dynamic proxies paired with annotations in my article at http://today.java.net/pub/a/today/2005/07/05/IOCAnnotation.html
Posted by: euxx on July 21, 2005 at 09:29 AM
-
Glenn Skinner notes that you could also modify the WeakProxy class so that the object it returns is already an MBean. In other words, if the CacheImpl object implements the CacheControlMBean interface, then you could wrap it in a proxy that also implements that interface, and forwards the methods of the interface to the CacheImpl object through a WeakReference.
However, if you do this you will run into a problem. The returned proxy will be an object of a class called something like $Proxy7, but the Standard MBean pattern requires the class that implements CacheControlMBean to be called CacheControl. There are two ways to work around this. The first is to use the class javax.management.StandardMBean, which is the usual way to lift the naming restriction for Standard MBeans. The second is to use an MXBean instead of a Standard MBean. MXBeans don't have this naming restriction.
Posted by: emcmanus on February 13, 2008 at 05:52 AM
|