Search |
||
Swing and Roundabouts 1P: Epoxy DTsPosted by evanx on August 18, 2006 at 12:36 PM PDT
Some background See the prequel Event DTs.
Using dynamic proxies to run methods in the EDT
The Spin framework uses dynamic proxies to make code EDT-safe. So we gonna try to do the same. Say we have a some methods that need to run in the EDT, as follows. public class NDialogHelper {
public int showConfirmDialog(Component parentComponent, Object message, String title, int option) {
return JOptionPane.showConfirmDialog(parentComponent, message, title, option);
}
...
}
But we also want to run these methods from code running in the background, ie. outside the EDT. For example an actionPerformed() method might start a SwingWorker thread to run a long task outside the EDT, so as not to block the EDT. This is important, so that the application does not appear to be hung while this long task is executing, ie. the user can still click on buttons and move windows around and such, since the Swing EDT is free to respond to these events. In order to use dynamic proxies (to intercept method invocations to our target object), we refactor out an interface corresponding to our class, and our class becomes an implementation of that interface. public interface NDialogHelper {
public int showConfirmDialog(Component parentComponent, Object message, String title, int option);
public void showMessageDialog(Component parentComponent, Object message, String title, int option);
public boolean showConfirmDialog(Object message);
public boolean showConfirmDialog(String format, Object ... args);
public void showMessageDialog(Object message);
public void showMessageDialog(String format, Object ... args);
public void showExceptionDialog(Throwable exception, String message, Object ... args);
...
}
where the implementation class is as follows. public class NDialogHelperImpl implements NDialogHelper {
public static final NDialogHelper dialogHelper = (NDialogHelper)
NProxyFactory.createProxy(new NDialogHelperImpl(), new NEdtInvoker());
private NDialogHelperImpl() {
}
public int showConfirmDialog(Component parentComponent, Object message, String title, int option) {
return JOptionPane.showConfirmDialog(parentComponent, message, title, option);
}
public boolean showConfirmDialog(Object message) {
return showConfirmDialog(null, message, null, JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION;
}
...
}
We expose this implementation to the application via a proxy object, namely the static dialogHelper singleton constructed above, using an NProxyFactory as below. The constructor above is private, since we only access the target via this proxy, using the NDialogHelper interface. public class NProxyFactory {
public static Object createProxy(Object target, NMethodInvoker invoker) {
NProxyInvocationHandler invocationHandler =
new NProxyInvocationHandler(target, invoker);
Object proxyObject = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler);
return proxyObject;
}
}
which is nothing out of the ordinary. Our createProxy() method takes an NMethodInvoker parameter, which is an interface as follows. public interface NMethodInvoker {
public Object invoke(Object target, Method method, Object[] args) throws Exception;
}
Our NProxyInvocationHandler will delegate method invocation to the given NMethodInvoker, as we'll see in a minute. Our NEdtInvoker is implemented as follows. public class NEdtInvoker implements NMethodInvoker {
public Object invoke(Object target, Method method, Object[] args) throws Exception {
Callable callable = createCallable(target, method, args);
return callEdt(callable);
}
protected Callable createCallable(final Object target, final Method method, final Object[] args) {
return new Callable() {
public Object call() throws Exception {
return method.invoke(target, args);
}
};
}
protected Object call(Callable callable) {
return call(callable, false);
}
protected Object callEdt(Callable callable) {
return call(callable, true);
}
protected Object call(Callable callable, boolean inEdt) {
final FutureTask task = new FutureTask(callable);
try {
if (inEdt && !SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeAndWait(task);
} else {
task.run();
}
return task.get();
} catch (InterruptedException ie) {
throw new NWrappedRuntimeException(ie, null);
} catch (InvocationTargetException ite) {
throw new NWrappedRuntimeException(ite.getCause(), null);
} catch (ExecutionException ee) {
throw new NWrappedRuntimeException(ee.getCause(), null);
}
}
public void runBackground(final Runnable runnable) {
SwingWorker worker = new SwingWorker() {
protected Object doInBackground() throws Exception {
try {
runnable.run();
} catch (Exception e) {
dialogHelper.showExceptionDialog(e, null);
}
return null;
}
};
worker.execute();
}
}
where for callEdt(), we check if isEventDispatchThread(), and if not then we invokeAndWait(). If we are gonna have classes with only some methods we wanna run inside the EDT, then we could annotate those methods, and implement the above invoke() method as follows. public Object invoke(Object target, Method method, Object[] args) throws Exception {
Callable callable = createCallable(target, method, args);
if (method.getAnnotation(InEdtAnnotation.class) == null) return call(callable);
return callEdt(callable);
}
which we might implement in an alternative NMethodInvoker, eg. NAnnotatedEdtInvoker, which might extend NEdtInvoker to override the invoke() method as above. Our InvocationHandler below delegates to the given NMethodInvoker, eg. NEdtInvoker. public class NProxyInvocationHandler implements InvocationHandler {
protected Object target;
protected NMethodInvoker invoker;
public NProxyInvocationHandler(Object target, NMethodInvoker invoker) {
super();
this.target = target;
this.invoker = invoker;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class declaringClass = method.getDeclaringClass();
if (declaringClass == Object.class) return invokeObject(proxy, method, args);
Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
return invoker.invoke(target, targetMethod, args);
}
...
}
which knows only about NMethodInvoker and doesn't have any DTs, ie. is reusable beyond our NEdtInvoker. OK, let's see if this works, by popping up a dialog, from a background (non-EDT) SwingWorker thread.
Supoib! Using EventBus for EDT-safety An alternative approach is made possible by eventbus.dev.java.net. In this case, our dialog displayer would be implemented as a service, which subscribes on the EventBus to "dialog display events." For example, we might implement MessageDialogEvent, which contains the string to display in the dialog. The dialog displayer would be invoked by EventBus in the EDT. So our application can then just publish events to be displayed onto the event bus, no matter whether we are in the EDT or not. This is quite elegant, so i think i'll write me a simplistic EventBus, to ensure i have a Quite Eventful weekend. I'll blog it into Swing and Roundabouts 1M: Emission DTs.
Conclusion
So this is one approach to building some EDT-safe helper classes, which we can use from code running in SwingWorker background threads. We avoid the isEventDispatchThread() and invokeAndWait() boiler-plate code, using dynamic proxies. The price we pay is having to maintain an interface for the implementation. The code can be found lurking in vellum.dev.java.net for now. Coming Up Swing and Roundabouts 5: Quite Gooey will present some reusable helper classes (into which the DTs of this blog are bundled), for building quick GUIs, with some tooling, beans binding by convention, validation and configuration via annotation and resource bundles. Before that, Plumber's Hack 2: Quite Writer will take quite a trip through the warez i wrote to write these blog articles. Please help! I'm working on Trip and Tick 4, about writing a Netbeans module to generate code in the editor, ie. manipulate java classes, and create new java classes, and such, along the lines of the following Web Start tool. If anyone has any pointers for me, eg. to sample module source code that does such manipulation and generation of java classes in Netbeans, please drop me a line (evanx at dev.java.net) - thanks! »
Related Topics >>
Java Desktop Comments
Comments are listed in date ascending order (oldest first)
|
||
|
|