I've got a lot of listeners that are registered using setListener methods, rather than addListener. So in order to allow multiple listeners to register to an object, I've got to use multiplexers. That's fine, but now I've got to create a multiplexer for every listener interface I have. So my question is: is it possible to implement Mux.create() as required for the following code?
AppleListener appleListener1 = new AppleProcessorA();
AppleListener appleListener2 = new AppleProcessorB();
AppleListener appleListenerMux = Mux.create(appleListener1, appleListener2);
Apple apple = new Apple();
apple.setListener(appleListenerMux);
OrangeListener orangeListener1 = new OrangeProcessorA();
OrangeListener orangeListener2 = new OrangeProcessorB();
OrangeListener orangeListenerMux = Mux.create(orangeListener1, orangeListener2);
Orange apple = new Orange();
orange.setListener(orangeListenerMux);
class Mux {
public static <T> T create(T... outputs) { }
}
I imagine this might be possible using reflection. Is there any reason using reflection would be a bad idea? (performance comes to mind)
It's possible using a dynamic Proxy.
The easiest way is to also pass in the desired interface as the first parameter to your Mux.create()
call. Otherwise, you'll have to use reflection to attempt to guess the desired interface from all the concrete listener instances provided (hard to determine if all listener objects implement several interfaces in common).
Here's the short of it:
public class Mux {
/**
* @param targetInterface
* the interface to create a proxy for
* @param instances
* the concrete instances to delegate to
* @return a proxy that'll delegate to all the arguments
*/
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> targetInterface, final T... instances) {
ClassLoader classLoader = targetInterface.getClassLoader();
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable {
for (T instance : instances) {
m.invoke(instance, args);
}
return null;
}
};
return (T) Proxy.newProxyInstance(classLoader,
new Class<?>[] { targetInterface }, handler);
}
}
Which you would use, for example, as follows:
Apple apple = new Apple();
AppleListener l1 = new AppleListenerA();
AppleListener l2 = new AppleListenerB();
apple.setListener(Mux.create(AppleListener.class, l1, l2));
apple.doSomething(); // will notify all listeners
This works by simply creating a dynamic Proxy
that is cast to the target type T
. That proxy uses an InvocationHandler
that merely delegates all method calls to the proxy to given concrete instances.
Note that while in general I finalize all parameters and local variables wherever possible, I only finalized T... instances
in this case to highlight the fact that if instances
was not final, then referencing it within an anonymous inner class wouldn't be allowed (you'll get a "Cannot refer to a non-final variable args inside an inner class defined in a different method").
Also note that the above assumes that the actual method calls don't return any meaningful (or useful) values, hence the handler
also returns null
for all method calls. You'll need to add quite a bit more code if you want to collect return values and return those as well in a meaningful way.
Alternatively, one can inspect all given instances
to determine the common interfaces they all implement, and pass all those to newProxyInstance()
. This makes Mux.create()
a lot more convenient to use, at the loss of some control over its behavior.
/**
* @param instances
* the arguments
* @return a proxy that'll delegate to all the arguments
*/
@SuppressWarnings("unchecked")
public static <T> T create(final T... instances) {
// Inspect common interfaces
final Set<Class<?>> commonInterfaces = new HashSet<Class<?>>();
commonInterfaces.addAll(Arrays.asList(instances[0].getClass()
.getInterfaces()));
// Or skip instances[0]
for (final T instance : instances) {
commonInterfaces.retainAll(Arrays.asList(instance.getClass()
.getInterfaces()));
}
// Or use ClassLoader.getSystemClassLoader();
final ClassLoader classLoader = instances[0].getClass().getClassLoader();
// magic
final InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(final Object proxy, final Method m, final Object[] args)
throws Throwable {
for (final T instance : instances) {
m.invoke(instance, args);
}
return null;
}
};
final Class<?>[] targetInterfaces = commonInterfaces
.toArray(new Class<?>[commonInterfaces.size()]);
return (T) Proxy.newProxyInstance(classLoader, targetInterfaces,
handler);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With