-
19 July 2010, 18:21
How Aspect-Oriented Programming Saved My Day
Until today, I had only ever heard of Aspect-Oriented Programming (AOP). I can't do any better at explaining what it is than Wikipedia or Software Engineering Radio so I won't really bother. Basically, there are problems that traditional Object-Oriented Programming (OOP) is bad at solving. AOP allows you to define a bit of cross-cutting code (advice) to be executed at specific points (pointcuts) that exist in your OO hierarchy. Some problems like security and logging are not solved well with OOP.
However, in my case, I am working on a giant project with tons of old code. Me and my colleagues are tasked with making it usable for visually impaired children. The visual impairments range from colorblindness to total blindness. Our recent problem was that there were some 950 locations where a
JComponent—JPanels andJButtons mostly—was instantiated. I know this because I used AspectJ to write an aspect (basically, a collection of pointcuts and advice), and Eclipse told me that it matched that many places. We needed to add aFocusListenerto every single one—at least for testing purposes. Doing this would have taken forever, and it would probably have been prone to errors. Instead, we decided to write an aspect, and it didn't take much time at all.In our aspect, we defined a short private inner class to be our focus listener. Then we got to the good stuff: the pointcut and the advice. The solution was so simple so we didn't separate the pointcut from the advice; we just did it all in one line.
after() returning(JComponent j): call(*.new(..)) { j.addFocusListener(new VisualFocusListener(j)); }In two lines of code, we have just added a
VisualFocusListenerto every JComponent instantiated throughout the project. It reads almost like it is in code: "after returning a JComponent from the call to a constructor, add a new focus listener to that returned JComponent". Will we leave it like this? Certainly not. There areJLabels that are getting a border for no reason right now. But within the advice, I can add any Java code that I want, and the advice can be more complicated, helping me to further limit what gets a listener attached to it.I think there is one further technical detail that is worth mentioning here. The pointcut "call(*.new(..))" matches any constructor that takes any arguments. I'm OK with this, because I have already enforced that the advice runs after a call to a constructor of a
JComponentsubclass by sayingreturning (JComponent j). If you want to limit the advice to a particular constructor, then replacecall(*.new(..))withcall(Foo.new(..)). This can be limited even further; for example:call(Foo.new(int)).Aspect-Oriented Programming is a powerful tool. It certainly kept my code a lot cleaner. And best of all, I got to use another acronym: DRY (Don't Repeat Yourself).
If you are interested, here is the entire aspect with my project-specific details omitted:
package ui; import java.awt.Color; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.border.Border; public aspect Focus { private class VisualFocusListener implements FocusListener { private JComponent jc; public VisualFocusListener(JComponent jc) { this.jc = jc; } public void focusGained(FocusEvent fe) { ... } public void focusLost(FocusEvent fe) { ... } } after() returning(JComponent j): call(*.new(..)) { j.addFocusListener(new VisualFocusListener(j)); } }