Aspect-oriented programming complements object-oriented programming
in many ways. One interesting complementary feature is behavior
composability. This means that it should be possible to compose a
class by adding behavior from different classes. OO uses
inheritance and many patterns to add behavior to existing classes.
AOP allows us to use mixins without changing the class
inheritance hierarchy or otherwise changing the code.
Mixins enable us to uniformly extend a set of classes with a set
of fields and methods. Java does not support mixin-based
programming. This article shows what mixins are and explains how
AOP constructs in AspectJ allow us to use this technique to isolate
crosscutting concerns, with an example. It starts with a plain Java
implementation and ends with an AspectJ 5 implementation.
AspectJ Tooling
AspectJ programs can be compiled either using the command-line
compiler ajc, which is included in the AspectJ
distribution, or by using better tooling support in the form of
AspectJ Development Tools for Eclipse (AJDT). All of the
benefits of an Integrated Development Environment are now also
available for AspectJ projects. I used the latest versions of
Eclipse and AJDT to compile these examples, which, as of the writing
of this article, were Eclipse 3.1.1 and AJDT 1.2. See the
Resources section for a very good
description of AJDT and how to create AspectJ projects in Eclipse.
Crosscutting Concern
A class is the fundamental unit of manipulation in Java.
Concerns that crosscut this modular unit could be isolated so that
such code is not spread out across the codebase. This will make
their modification easier. It should also be easy to add a concern
without changing the code that this will affect.
Our crosscutting concern is the notification of changes to Java
bean properties. JavaBeans can have bound properties. This means
that we can register listeners to get notification when the value
of the property changes. The java.beans package
contains the classes and interfaces required to implement this
functionality.
The following code shows the plain Java implementation.
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
public class Bean implements Serializable {
private String name;
private PropertyChangeSupport support =
new PropertyChangeSupport(this);
public void addPropertyChangeListener
(PropertyChangeListener listener){
support.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener
( String propertyName,
PropertyChangeListener listener){
support.addPropertyChangeListener(propertyName,
listener);
}
public void removePropertyChangeListener
( String propertyName,
PropertyChangeListener listener) {
support.removePropertyChangeListener( propertyName,
listener);
}
public void removePropertyChangeListener
(PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}
public void hasListeners( String propertyName ) {
support.hasListeners(propertyName);
}
void firePropertyChange( Bean b,
String property,
String oldval,
String newval) {
support.firePropertyChange( property,
( oldval == null ) ?
oldval :
new String(oldval),
new String(newval));
}
public String getName() {
return name;
}
public void setName( String name ) {
firePropertyChange( this,
"name",
getName(),
name );
this.name = name;
}
}
The important points in the code are:
We can delegate listener maintenance tasks to the
PropertyChangeSupport class.
The firePropertyChange method, which is called
after the property changes, notifies the
PropertyChangeListeners.
The code shown above mixes event listener code with a simple Java
bean that declares properties and getter and setter
methods for them. We could separate the event listener concern into
a separate class that all JavaBeans must extend, but that does not
ensure that the concern is truly separable. It is still linked
by the OO inheritance hierarchy. This is where AOP complements OO,
by introducing the concept of a mixin, which is not properly
addressed by an object-oriented language like Java.
The following is the JUnit test case based on the self-shunt
pattern (see Resources).
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import junit.framework.TestCase;
public class BeanTestCase extends TestCase
implements PropertyChangeListener{
private String lastName;
public void setUp() throws Exception{
super.setUp();
}
public void TearDown() throws Exception{
super.setUp();
}
public void propertyChange(PropertyChangeEvent e){
System.out.println("Property [" +
e.getPropertyName() +
"[ changed from " +
e.getOldValue() + " to " +
e.getNewValue() );
lastName = e.getNewValue() == null ? null :
(String)e.getNewValue();
}
public void testPropertyChange(){
Bean b = new Bean();
b.addPropertyChangeListener( "name", this );
b.setName( "Test" );
if( lastName != null )assertEquals( b.getName() ,
lastName );
b.setName( "Test1" );
if( lastName != null )assertEquals( b.getName() ,
lastName );
}
}
The JUnit TestCase shown above registers itself as a
PropertyChangeListener so that when the bound property
changes, it is notified via a
propertyChange(PropertyChangeEvent e) method that is
called. The parameter PropertyChangeEvent contains
both the old and the new values. This test case plays the
role of the listener, thereby obviating the need for a separate
listener.
Now let us see how aspect-oriented mixin programming enables us
to specify crosscutting concerns in one place and compose them with
existing classes without changing the Java code.