Writing Mixins using AspectJ Blog

    {cs.r.title}



              
                                    

    Contents
    AspectJ Tooling
    Crosscu tting 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 isbehavior 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 5implementation.

    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 theResources 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 thePropertyChangeSupport class.
    • The firePropertyChange method, which is called after the property changes, notifies thePropertyChangeListeners.
    The code shown above mixes event listener code with a simple Java bean that declares properties and getter and settermethods 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 aPropertyChangeListener so that when the bound property changes, it is notified via apropertyChange(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.

     
    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)); } } 
    
    In the code shown above the line:
     
    public PropertyChangeSupport Bean.support = new PropertyChangeSupport(this); 
    
    introduces a new data member, and this code
     
    public void Bean.addPropertyChangeListener( PropertyChangeListener listener){ support.addPropertyChangeListener(listener); } 
    
    introduces a new method. We can also see that inside of the methodaddPropertyChangeListener, 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.

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

    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 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 thesetName method with a parameter of the typeString.

    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 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 proceedmethod.

    Our around advice uses thisJoinPointStaticPart.getSignature(), which is the signature of the join point. In this case, the signature is thesetName method (see the API docs in the AspectJ distribution for more information). The getFieldmethod strips out "set" and gets the property name, which it uses to construct a reflected java.lang.Field. ThegetField 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 setNamemethod 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.

     
    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)); } } 
    

    The code shown above uses the interfacePropertySupport, where previously we usedBean. 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.

     
    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; } 
    
    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 inPropertySupportAspect.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 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 isElementType.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.

     
    package com.blueprint.util.aspectj5.test; import java.lang.annotation.*; import java.lang.*; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) public @interface javaBean { } 
    
    The second annotation is used to annotate the "setter" method that changes the value of the property, so the target isElementType.METHOD. This means that it can only be used to annotate methods.
     
    package com.blueprint.util.aspectj5.test; import java.lang.annotation.*; import java.lang.*; @Retention( RetentionPolicy.RUNTIME ) @Target({ ElementType.METHOD }) public @interface propertyChanger { } 
    
    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.
     
    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; } } 
    
    The pointcut in PropertySupportAspect.aj has to be rewritten as:
     
    pointcut callSetter( Bean b ) : call( @propertyChanger * *(..) ) && target( b ); 
    
    @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.
     
    package com.blueprint.util.aspectj5.test; import com.blueprint.util.aspectj5.test. PropertySupportAspect5.PropertySupport; public aspect BeanSupportAspectj { declare parents: @javaBean * implements PropertySupport; } 
    

    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:

     
    b.addPropertyChangeListener( "name", this );
    
    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

      
    http://today.java.net/im/a.gif