Skip to main content

Writing Mixins using AspectJ

December 15, 2005

{cs.r.title}









Contents
AspectJ Tooling
Crosscutting Concern
What Exactly Is a Mixin?
Inter-Type Declarations
or Introductions
The AOP Implementation
Without Mixins
AOP Mixin Implementation
AOP Mixin Implementation
with AspectJ 5 and JSE 5.0
Incremental Compilation Bug
Conclusion
Resources

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 "http://www.eclipse.org/aspectj">AspectJ 5 implementation.

AspectJ Tooling

AspectJ programs can be compiled either using the command-line
compiler ajc, which is included in the "http://www.eclipse.org/aspectj/downloads.php">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 "http://www.eclipse.org/ajdt/downloads">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.

[prettify]
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;

    }
}
[/prettify]
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).

[prettify]
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 );
       }
}
[/prettify]
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.







What Exactly Is a Mixin?

A mixin is also called an "abstract subclass." It is a class
that is not used on its own. It is usually combined with other
classes to create a composed class. Though this can also be done
with languages that support multiple inheritance, AOP allows us to
do this with inter-type declarations and does not require us to set
up an inheritance hierarchy.

In our example, the listener notification code is a mixin. It is
not very useful on its own and should be combined with JavaBeans
that need this functionality.

Inter-Type Declarations or Introductions

This is also called static-crosscutting. The AspectJ language
enables us to change the static structure of classes by introducing
variables and methods and by changing the inheritance hierarchy. So the
listener notification code that is the common functionality can be
uniformly applied as a concern to a set of JavaBeans. Let us see
how this is done.

The AOP Implementation Without Mixins

The code shown below (BeanAspect.aj) is the aspect that
uses inter-type declarations. AspectJ uses the ".aj"
extension to denote aspects.

[prettify]
package com.blueprint.util.test;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;

import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Pointcut;

public aspect BeanAspect {

 public PropertyChangeSupport Bean.support =
                        new PropertyChangeSupport(this);

 public void Bean.addPropertyChangeListener(
                    PropertyChangeListener listener){

    support.addPropertyChangeListener(listener);

 }

 public void Bean.addPropertyChangeListener
                            ( String propertyName,
                  PropertyChangeListener listener){

    System.out.println( "PropertyChangeListener added" );
    support.addPropertyChangeListener(propertyName,
                                              listener);

 }

 public void Bean.removePropertyChangeListener
                            ( String propertyName,
                  PropertyChangeListener listener) {

    support.removePropertyChangeListener(propertyName,
                                         listener);

 }

 public void Bean.removePropertyChangeListener
                    (PropertyChangeListener listener){

    support.removePropertyChangeListener(listener);

 }

 public void Bean.hasListeners( String propertyName ) {

    support.hasListeners(propertyName);

 }

 pointcut callSetter( Bean b ) : call( public void
     com.blueprint.util.test.Bean.setName( String ) )&&
                                            target( b );

 void around( Bean b ) : callSetter( b )  {

   String propertyName =
      getField( thisJoinPointStaticPart.getSignature() ).
                                                getName();
   System.out.println("The property is ["+propertyName+"]");
   String oldValue = b.getName();
   proceed( b );
   firePropertyChange( b, propertyName, oldValue, b.getName());

 }

 private Field getField( Signature signature ){

    Field field = null;
    System.out.println( "Getting the field name of ["+
                           signature.getName() + "]" );

    try{
         String methodName = signature.getName();
         String propertyName = methodName.
                       substring( 3,methodName.length() ).toLowerCase();
         field = signature.getDeclaringType().
                                    getDeclaredField( propertyName );
    }catch( NoSuchFieldException nsfe ){
       nsfe.printStackTrace();
    }
  return field;

 }

 void firePropertyChange( Bean b,
                          String property,
                          String oldval,
                          String newval) {

    System.out.println( "The property is [" + property + "]");
    System.out.println( "The old value is [" + oldval + "]");
    System.out.println( "The new value is [" + newval + "]");
    b.support.firePropertyChange( property,
                                  ( oldval == null ) ?
                                     oldval :
                                     new String(oldval),
                                  new String(newval));

 }
}
[/prettify]
In the code shown above the line:
[prettify] public PropertyChangeSupport Bean.support =
                        new PropertyChangeSupport(this);
[/prettify]
introduces a new data member, and this code
[prettify]
 public void Bean.addPropertyChangeListener(
                    PropertyChangeListener listener){
    support.addPropertyChangeListener(listener);
 }
[/prettify]
introduces a new method. We can also see that inside of the method
addPropertyChangeListener, we are using the newly
introduced variable support. This is the first step
towards the promised mixin implementation.

Now, the issue with this code is that the bean name has to be
hard-coded. Due to this, there is no way to mix this with different
JavaBeans, even though it separates the boilerplate code from the
bean.

This can be solved by making all of the JavaBeans implement an
interface and using AspectJ to introduce methods to the interface.
In this case, the interface name is hard-coded instead of the bean
names. The following code snippet will show what we mean by
this.

[prettify]
public interface IntertypeInterface {
}
public aspect IntertypeAspect {
       public void IntertypeInterface.addTestMethod(){
       }
}
public class Foo implements IntertypeInterface{
       public void addTestMethod(){
               System.out.println(" Introduced method" );
       }
}
[/prettify]

However, this still results in a OO hierarchy. So we are not
going to use this idea.

In BeanAspect.aj, the lines in bold form the core part of
the AspectJ implementation. callSetter( Bean b ) is
the name of the pointcut, and the definition of the pointcut is to
the right of the colon. A "http://www.eclipse.org/aspectj/doc/released/progguide/starting-aspectj.html#d0e194">
named pointcut
declaration means that in the around
advice that follows, we can use the name of the pointcut and we need
not repeat the pointcut definition. The pointcut definition

call( public void com.blueprint.util.test.Bean.setName(
String ) )&& target( b )
picks out any call to the
setName method with a parameter of the type
String.

We see that this named pointcut also takes a parameter of the type
Bean, which means that every join point picked out by
this makes available an object of this type. This is also called
the exposed context. This parameter is available inside of any
advice that uses this pointcut, as we can see in the code.
target( b ) means that the pointcut definition applies
if the target of the method call joinpoint is a
Bean. It should be noted that b on the
right side matches the parameter type Bean on the left
side.

The description of the around advice is taken from "http://www.eclipse.org/aspectj/doc/released/progguide/semantics.html">
the language semantics section
of the AspectJ guide: "Around
advice runs in place of the join point it operates over, rather
than before or after it. Because around is allowed to return a
value, it must be declared with a return type, like a method." Even
though our around advice substitutes our join point, we can still
call the original functionality by using the proceed
method.

Our around advice uses this
JoinPointStaticPart.getSignature(), which is the
signature of the join point. In this case, the signature is the
setName method (see the API docs in the AspectJ
distribution for more information). The getField method
strips out "set" and gets the property name, which it uses to
construct a reflected java.lang.Field. The
getField method is separated from the around advice
because we want to keep that as a utility method and not include it
in the advice.

The main point here is that every time the setName
method is called, our advice takes a copy of the value of the
property, lets the original method proceed, and, after the new value
is set, calls the firePropertyChange method. Now we
see that the code to be introduced to our beans has been separated
cleanly and is now reusable.







AOP Mixin Implementation

In the mixin implementation PropertySupportAspect.aj,
behavior that is to be composed with the JavaBean is declared as an
interface in our aspect. Separating the interface that we are going
to mix takes us one step closer to the mixin.

[prettify]
package com.blueprint.util.mixin.test;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import org.aspectj.lang.Signature;

public aspect PropertySupportAspect {

    PropertyChangeSupport PropertySupport.support =
                            new PropertyChangeSupport(this);

    public interface PropertySupport{

       public void addPropertyChangeListener
                    ( PropertyChangeListener listener );

       public void addPropertyChangeListener
                            ( String propertyName,
                  PropertyChangeListener listener );

       public void removePropertyChangeListener
                            ( String propertyName,
                  PropertyChangeListener listener );

       public void removePropertyChangeListener
                            ( PropertyChangeListener listener );

       public void hasListeners( String propertyName );

       public void firePropertyChange( Bean b,
                                       String property,
                                       String oldval,
                                       String newval );
    }

    public void PropertySupport.addPropertyChangeListener
                              (PropertyChangeListener listener){

          support.addPropertyChangeListener(listener);

    }

    public void PropertySupport.addPropertyChangeListener
                      ( String propertyName,
                        PropertyChangeListener listener){

          support.addPropertyChangeListener( propertyName,
                                             listener);

    }



    public void PropertySupport.removePropertyChangeListener
                  ( String propertyName,
                    PropertyChangeListener listener){
            support.removePropertyChangeListener( propertyName,
                                                  listener);
   }


   public void PropertySupport.removePropertyChangeListener
                              (PropertyChangeListener listener) {

            support.removePropertyChangeListener(listener);

   }

   public void PropertySupport.hasListeners(String propertyName) {

            support.hasListeners(propertyName);

   }

   pointcut callSetter( Bean b )
     : call( public void
            com.blueprint.util.mixin.test.Bean.setName( String ) )
                                               && target( b );

   void around( Bean b ) : callSetter( b )  {

        String propertyName =
         getField( thisJoinPointStaticPart.getSignature() ).
                                                  getName();
        System.out.println( "The property is [" +
                                       propertyName + "]" );
        String oldValue = b.getName();
        proceed( b );
        b.firePropertyChange( b,
                              propertyName,
                              oldValue,
                              b.getName());

      }

      private Field getField( Signature signature ){

        Field field = null;
        System.out.println( "Getting the field name of [" +
                                       signature.getName() + "]" );

        try{
                String methodName = signature.getName();
                String propertyName = methodName.
                       substring( 3,methodName.length() ).toLowerCase();
                field = signature.getDeclaringType().
                                    getDeclaredField( propertyName );
        }catch( NoSuchFieldException nsfe ){
            nsfe.printStackTrace();
        }
       return field;

      }

      public void PropertySupport.firePropertyChange( Bean b,
                                                      String property,
                                                      String oldval,
                                                      String newval) {
            System.out.println( "The property is [" + property + "]");
            System.out.println( "The old value is [" + oldval + "]");
            System.out.println( "The new value is [" + newval + "]");
            support.firePropertyChange( property,
                                       ( oldval == null ) ?
                                            oldval :
                                            new String(oldval),
                                            new String(newval));

      }
}
[/prettify]

The code shown above uses the interface
PropertySupport, where previously we used
Bean. Remember, we do not want our JavaBeans to
implement any interface to make this code possible. Instead,
AspectJ can change the inheritance hierarchy for us. In the
following code, we are introducing a super-interface that our beans
implement.

[prettify]
package com.blueprint.util.mixin.test;

import com.blueprint.util.mixin.test.PropertySupportAspect.PropertySupport;
import com.blueprint.util.mixin.test.*;

public aspect BeanSupport {

     declare parents: Bean implements
               PropertySupportAspect.PropertySupport;

}
[/prettify]
Run the test BeanTestCase.java, which should show a green
bar. Now the benefits of such an approach are clearly visible.

  • Each bean would have needed this code to handle bound
    properties. Now the listener code is localized.
  • The interface that we have defined in
    PropertySupportAspect.aj is well-defined.
  • We can compose this feature with other classes in a
    non-invasive way. Actually, we can argue that the abstraction level
    has been raised, letting AspectJ handle the low-level tasks.
  • On the whole, this is more modular and more closely reflects the way the
    developer thinks.







AOP Mixin Implementation with AspectJ 5 and JSE 5.0

AspectJ5 is the latest release, targeting J2SE 5.0 features like
generics and "http://java.sun.com/j2se/1.5.0/docs/guide/language/annotations.html">
annotations
. Annotations enable us to add metadata to code.
This metadata can be used in many ways to process the code. AspectJ
5 has added support for writing pointcuts to pick out join points
based on annotations. We can read the description of annotations
and look at a few examples to understand their meaning, especially
about retention policies and targets, which are briefly explained
below.

The enhanced pointcut language adds more power to AspectJ. Let
us rewrite our example using annotations. Since annotations add
metadata to describe Java code, we define two annotations to
describe important features of our example. The first annotation is
used to annotate the JavaBean, so the target is
ElementType.TYPE, which means that it can only be used
to annotate classes. The line
@Retention(RetentionPolicy.RUNTIME) is the retention
policy, which means that our metadata is accessible at runtime.

[prettify]
package com.blueprint.util.aspectj5.test;

import java.lang.annotation.*;
import java.lang.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })

public @interface javaBean {

}
[/prettify]
The second annotation is used to annotate the "setter" method that
changes the value of the property, so the target is
ElementType.METHOD. This means that it can only be
used to annotate methods.
[prettify]
package com.blueprint.util.aspectj5.test;

import java.lang.annotation.*;
import java.lang.*;

@Retention( RetentionPolicy.RUNTIME )
@Target({ ElementType.METHOD })

public @interface propertyChanger {

}
[/prettify]
We can see below that the JavaBean has been annotated using our
custom annotations. Both @javaBean() and
@propertyChanger() are called marker
annotations
. Readers familiar with marker interfaces should see
a comparison here. A marker annotation does not have any values
specified within braces.
[prettify]

package com.blueprint.util.aspectj5.test;
import java.io.Serializable;

@javaBean()
public class Bean implements Serializable{

    private String name;

    public String getName() {

            return name;

    }

    @propertyChanger()
    public void setName( String name ) {
            this.name = name;
    }
}
[/prettify]
The pointcut in PropertySupportAspect.aj has to be rewritten
as:
[prettify]
pointcut callSetter( Bean b )
    : call( @propertyChanger * *(..) ) && target( b );
[/prettify]
@propertyChanger * *(..) will match any method with
any name, in any type, taking any arguments. The aspect below will
match our annotated bean now. @javaBean * matches any
type with a @javaBean annotation.
[prettify]
package com.blueprint.util.aspectj5.test;
import com.blueprint.util.aspectj5.test.
            PropertySupportAspect5.PropertySupport;

public aspect BeanSupportAspectj {

     declare parents: @javaBean * implements PropertySupport;

}
[/prettify]

The flexibility and power of this new pointcut definition based
on annotations is now evident. Our AspectJ 5 implementation is
similar to our previous mixin that does not use annotations, but
this is just a simple example of AspectJ's annotation support. The
benefits of annotations and AspectJ5 could be the topic of a whole
article.

Incremental Compilation Bug

As of the writing of this article, an Inter-Type Declaration
(ITD) bug shows in the mixin implementations. The indication is at
the line:

[prettify]b.addPropertyChangeListener( "name", this );
[/prettify]
in the JUnit Test case in the Eclipse editor. I have seen a
solution that flips a flag to do a full build by checking the first
check box in the Other tab in Project -> Properties -> AspectJ
Compiler, but this could not remove the error. This does not impact
the code, though, and the test case should succeed.

Conclusion

Practical use of AOP is a very important factor in its adoption.
This article demonstrates one such use case with an example
focusing on how AOP complements OO by enabling us to write mixins
in a clean way. We have also seen that improving the modularity of
code is always beneficial. AOP implementations also add
extensibility, composability, and reusability to code in a way that
is richer than what OO can manage. Moreover, old concepts like
mixins are used with newer implementations with the advent of
AspectJ and dynamically typed scripting languages. I should also
add that since tools like the AspectJ Development Tools for Eclipse
(AJDT) are stabilizing after the introduction of the new features,
there is a chance that we might uncover a bug when working with the
latest JDK 5 features in AspectJ 5.

Resources

width="1" height="1" border="0" alt=" " />
Mohan Radhakrishnan has been a professional Java developer since 1997, and currently works for Accenture solutions division in India.
Related Topics >> Programming   |