Archive for August, 2006
the “Event-dispatching-thread-enforcer” aspect
After having read about SwingWorker, SwingUtilities.invokeLater and SwingUtilities.invokeLaterAndWait (here and here) and finally having understood the true implications of the single-threaded GUI model used in java I got kind of scared. Writing java GUI:s correctly in a multithreaded environment is hard. Very hard. Even experienced programmers will at times forget handing over to the event-dispatcher or even at times use the SwingWorker incorrectly (which might be even worse, since using the SW will look like “hey, I know what I am doing here”), thereby introducing bugs that will appear rarely and in a undetermined manner.
I was thinking about this brittleness for a while. Then a crazy idea popped up about how to track the mistakes and temporarily fix these bugs all at once.
The idea is as follows:
Commonly all gui-classes and widgets are stored in a separate package in the application. What if we add an aspect to all methods on all classes in those packages that:
- Alerts (in the log for instance) if the call is done in the wrong thread.
- Hands over the call to the event-dispatcher (as it should have been called from the start).
This aspect could for instance be used during development and even maybe during testing as a safety-net.
Putting this idea into code was straightforward. I used Aspectwerkz landed in the following…
EventDispatcherThreadEnforcerAspect:
[ftf w="470" h="500" def="js.xml"]
package aoptest.aspects;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import javax.swing.SwingUtilities;
public class EventDispatcherThreadEnforcerAspect {
/**
* Will check whether we are using the correct thread for this call
* and alert + handover if necessary.
*/
public Object threadCheck(final JoinPoint joinPoint) throws Throwable {
if (!SwingUtilities.isEventDispatchThread()) {
logMistake(joinPoint);
return performCallInEventDispatcherThread(joinPoint);
}
else
return joinPoint.proceed();
}
private void logMistake(JoinPoint joinPoint) {
System.out.println(”ERROR: GUI-component called by thread: ” + Thread.currentThread().getName());
System.out.println(”Call came from: ” + deduceCallingClassAndMethod(joinPoint));
System.out.println(”Will hand over to event-dispatcher for now.”);
}
/**
* Perform call in event dispatcher instead
*/
private Object performCallInEventDispatcherThread(final JoinPoint joinPoint) throws Throwable {
final Object[] result = new Object[1];
final Throwable[] throwable = new Throwable[1];
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
try {
result[0] = joinPoint.proceed();
} catch (Throwable t) {
throwable[0] = t;
}
}
});
if (throwable[0]!=null)
throw throwable[0];
else
return result;
}
/**
* Will loop through the call stack and return info about the
* stack element just before the joinpoint-caller-element.
* This will be the “sinning”-class and method.
*/
private String deduceCallingClassAndMethod(JoinPoint joinPoint) {
Class callerClass = joinPoint.getCallerClass();
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
for (int i = 0; i < stackTrace.length; i++) {
StackTraceElement stackTraceElement = stackTrace[i];
if (stackTraceElement.getClassName().equals(callerClass.getName()))
return infoAboutStackElement(stackTrace, i+1);
}
return "
}
private String infoAboutStackElement(StackTraceElement[] trace, int i) {
return trace[i].getClassName() + “.” +
trace[i].getMethodName() + ” (line: ” +
trace[i].getLineNumber() + “)”;
}
}
[/ftf]
aop.xml:
[ftf w="470" h="200" def="html.xml"]
[/ftf]
That is all we need. Let me show you how it works.
Presume we have a gui component looking like this:
[ftf w="470" h="180" def="js.xml"]
public class MyGUIComponent {
public void foo() {
boolean isEventDispatcher = SwingUtilities.isEventDispatchThread();
System.out.println(”MyGUIComponent.foo: isEventDispatcher=” + isEventDispatcher);
}
}
[/ftf]
As you can see it just prints whether the caller us using the correct thread or not.
Let’s call the foo method in two ways. First of all incorrectly and then correctly, just like this:
[ftf w="470" h="240" def="js.xml"]
public class Test {
public static void main(String[] args) {
System.out.println(”– SINNER ————”);
new MyGUIComponent().foo();
System.out.println(”– GOOD GUY ———-”);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MyGUIComponent().foo();
}
});
}
}
[/ftf]
Running the “Test”-class above without any aop we get:
-- SINNER ------------
MyGUIComponent.foo: isEventDispatcher=false
-- GOOD GUY ----------
MyGUIComponent.foo: isEventDispatcher=true
If we now instead run it with the aspect added we get:
-- SINNER ------------
ERROR: GUI-component called by thread: main
Call came from: aoptest.Test.main (line: 10)
Will hand over to event-dispatcher for now.
MyGUIComponent.foo: isEventDispatcher=true
-- GOOD GUY ----------
MyGUIComponent.foo: isEventDispatcher=true
Note that the component now is called with the correct thread both times and that we get a logging statement about where the call came from.
I imagine this could be quite useful.
And it least it shows another usage of aspects than the worn out “trace-logging” and “call-timer” examples.
1 commentGeneric DAOs
Not that DAOs always is the way to go when writing your hibernate application … but this article nevertheless shows a clever way of using generics in conjunction with introduction via Spring framework.
Concise, to-the-point and read worthy:
http://www-128.ibm.com/developerworks/java/library/j-genericdao.html
Dragons’ Den third season
Third season of the entrepreneurial (well sort of) show Dragons’ Den is now broadcasted on BBC (and hopefully soon by TV8):
http://www.bbc.co.uk/dragonsden/
Follow up 2006-08-22
Got this from a representative at TV8:
Hej Tobias och tack för ditt mail,
Vi hoppas kunna köra även tredje säsongen av Dragons’ här. Men det finns inga beslut fattade ännu.
Visual Studio .NET
My current project is amusingly diverse. Lately I have been involved in some basic C#-programming in Visual Studio .NET.
I am very surprised to see that aspx-pages are not verified (for correctness) in compile time. Also surprised to see that Mirosoft intellisense does not work for those pages. I short: If you don’t get your code snippets right you won’t know until runtime. Whoohoo.
Come on … how 90:s-kind-of-IDE is that?
No commentsEric Evans: Domain-Driven Design Three-Day Workshop at Citerus
Eric Evans, renowned author and internationally recognized for his significant contribution in the area of Domain-Driven Design, DDD, returns to Citerus (the company where I work) fall 2006 to host a three day in-depth workshop on Domain-Driven Design.
Last time Eric visited Sweden and Citerus in the fall of 2005, the seminar sold out in just a few days.
See the course description and syllabus here.
Welcome!
No comments
This blog is written by me, Tobias Hill.