using gradle with aspectj

By breskeby | February 22, 2010

In this post I want to show you how easy it is to build your aspectj projects with gradle. IMHO gradle is the most flexible, versatile build tool for JVM based projects. It fully supports ant and integrates well in maven environments.
But lets dive into the example I prepared.

The example project is based on the “Bean Example” provided by the ajdt plugin for eclipse. This example contains three classes:

We have a basic bean class named Point:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.breskeby.bean;

class Point {

    private int x = 0;
    private int y = 0;

    public int getX(){
        return x;
    }
    public int getY(){
        return y;
    }

     public void setX(int newX) {
        this.x = newX;
    }

    public void setY(int newY) {
        this.y = newY;
    }
}

Now I want to use this bean with full property change listener support without pollute my Point source code. So I create an aspectj BoundPoint which weaves the propertychange support into the bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/*
 * Copyright (c) 1998-2002 Xerox Corporation.  All rights reserved.
 *
 * Use and copying of this software and preparation of derivative works based
 * upon this software are permitted.  Any distribution of this software or
 * derivative works must comply with all applicable United States export
 * control laws.
 *
 * This software is made available AS IS, and Xerox Corporation makes no
 * warranty about the software, its performance or its conformity to any
 * specification.
 */


package com.breskeby.bean;

import java.beans.*;
import java.io.Serializable;

/*
 * Add bound properties and serialization to point objects
 */


aspect PointAspect {
  /*
   * privately introduce a field into Point to hold the property
   * change support object.  `this' is a reference to a Point object.
   */

  private PropertyChangeSupport Point.support = new PropertyChangeSupport(this);

  /*
   * Introduce the property change registration methods into Point.
   * also introduce implementation of the Serializable interface.
   */

  public void Point.addPropertyChangeListener(PropertyChangeListener listener){
    support.addPropertyChangeListener(listener);
  }

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

    support.addPropertyChangeListener(propertyName, listener);
  }

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

  public void Point.removePropertyChangeListener(PropertyChangeListener listener) {
    support.removePropertyChangeListener(listener);
  }

  public void Point.hasListeners(String propertyName) {
    support.hasListeners(propertyName);
  }

  declare parents: Point implements Serializable;

  /**
   * Pointcut describing the set<property> methods on Point.
   * (uses a wildcard in the method name)
   */

  pointcut setter(Point p): execution( public void Point.set*(*) ) && target(p);

  /**
   * Advice to get the property change event fired when the
   * setters are called. It's around advice because you need
   * the old value of the property.
   */

  void around(Point p): setter(p) {
        String propertyName =
      thisJoinPointStaticPart.getSignature().getName().substring("set".length());
        int oldX = p.getX();
        int oldY = p.getY();
        proceed(p);
        if (propertyName.equals("X")){
      firePropertyChange(p, propertyName, oldX, p.getX());
        } else {
      firePropertyChange(p, propertyName, oldY, p.getY());
        }
  }

  /*
   * Utility to fire the property change event.
   */

  void firePropertyChange(Point p, String property, double oldval, double newval) {
        p.support.firePropertyChange(property, new Double(oldval), new Double(newval));
  }
}

To test the correct behaviour of this aspect we use a junit test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.breskeby.bean;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;

public class PointTest {

    //class under test
    private Point cut;
   
    @Before public void setup(){
        cut = new Point();
    }
   
    @Test
    public void testPropertyChangeEventFired(){
        PropertyChangeListener changeListenerMock =
                   mock(PropertyChangeListener.class); 
        cut.addPropertyChangeListener(changeListenerMock);
        cut.setX(1);
        verify(changeListenerMock,times(1))
                   .propertyChange((PropertyChangeEvent) anyObject());
    }
}

After explaining the boring part of the example we can focus on the automatic build now.

As a starting point we use a simple build file that uses the java plugin:

1
2
3
4
5
6
7
8
9
10
11
apply id:'java'

repositories {
    mavenCentral()
}

dependencies{
    compile "aspectj:aspectjlib:1.5.3"
    testCompile "junit:junit:4.7"
    testCompile "org.mockito:mockito-all:1.8.2"
}

When running “gradle test” gradle tells us, that something went wrong:

1
2
3
4
5
6
7
8
Example/src/test/java/com/breskeby/bean/PointTest.java:26: cannot find symbol
symbol  : method addPropertyChangeListener(java.beans.PropertyChangeListener)
location: class com.breskeby.bean.Point
        cut.addPropertyChangeListener(changeListenerMock);
           ^
1 error

FAILURE: Build failed with an exception.

To get this running we need to replace the compileJava task of the java plugin by a custom task that uses the aspectj compiler. The easiest way to get this working is to use the iajc ant task. the aspectj compiler demands additional configurations to setup the classpath for the iajc task and the classpaths for the inpath an aspectpath. To add custom configurations we simple add the following lines to our build file

1
2
3
4
5
configurations {
    ajc
    aspects
    ajInpath
}

in our dependency block we add the aspect ant task to ajc. Since we don’t use external dependencies for inpath and aspectpath we needn’t add here anything. The complete dependency section for our tiny example is shown here:

1
2
3
4
5
6
dependencies{
    ajc "aspectj:aspectjtools:1.5.3"
    compile "aspectj:aspectjrt:1.5.3"
    testCompile "junit:junit:4.7"
    testCompile "org.mockito:mockito-all:1.8.2"
}

As mentioned we have to replace the compileJava task with our own one. Our custom task looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
task compileJava(dependsOn: JavaPlugin.PROCESS_RESOURCES_TASK_NAME, overwrite: true) < < {
    ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
    ant.iajc(source:sourceCompatibility, target:targetCompatibility, destDir:sourceSets.main.classesDir.absolutePath, maxmem:"512m", fork:"true",
        aspectPath:configurations.aspects.asPath, inpath:configurations.ajInpath.asPath, sourceRootCopyFilter:"**/.svn/*,**/*.java",classpath:configurations.compile.asPath){  
       
        sourceroots{
            sourceSets.main.java.srcDirs.each{
                pathelement(location:it.absolutePath)
            }      
        }
    }
}

running now “gradle test” should work and the test succeeds. The complete working build.gradle file looks like that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apply id:'java'

repositories {
    mavenCentral()
}

configurations {
    ajc
    aspects
    ajInpath
}

dependencies{
    ajc "aspectj:aspectjtools:1.5.3"
    compile "aspectj:aspectjrt:1.5.3"
    testCompile "junit:junit:4.7"
    testCompile "org.mockito:mockito-all:1.8.2"
}

task compileJava(dependsOn: JavaPlugin.PROCESS_RESOURCES_TASK_NAME, overwrite: true) < < {
    ant.taskdef( resource:"org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath)
    ant.iajc(source:sourceCompatibility, target:targetCompatibility, destDir:sourceSets.main.classesDir.absolutePath, maxmem:"512m", fork:"true",
        aspectPath:configurations.aspects.asPath, inpath:configurations.ajInpath.asPath, sourceRootCopyFilter:"**/.svn/*,**/*.java",classpath:configurations.compile.asPath){  
       
        sourceroots{
            sourceSets.main.java.srcDirs.each{
                pathelement(location:it.absolutePath)
            }      
        }
    }
}

I wrapped all aspectj specific parts of the build script to a aspectj plugin available at
http://github.com/breskeby/gradleplugins/raw/0.9-upgrade/aspectjPlugin/aspectJ.gradle

If you’re already running gradle version 0.9+ you can use this plugin and the build file looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
apply url:'http://github.com/breskeby/gradleplugins/raw/0.9-upgrade/aspectjPlugin/aspectJ.gradle'

repositories {
    mavenCentral()
}

dependencies{
    ajc "aspectj:aspectjtools:1.5.3"
    compile "aspectj:aspectjrt:1.5.3"
    testCompile "junit:junit:4.7"
    testCompile "org.mockito:mockito-all:1.8.2"
}

This sample project is available at github and comments and suggestions are appreciated.

regards,
René

10 Responses to “using gradle with aspectj”

  1. Andrew Eisenberg Says:
    March 12th, 2010 at 06:08

    Nice work!

  2. speed up your build with gradle | (b)logbuch Says:
    March 12th, 2010 at 23:35

    [...] my last post, I explained how to add aspectj support to gradle. Some tasks (like the shown iajc task) in complex [...]

  3. Jayhawk Says:
    April 13th, 2010 at 21:13

    Great Post!

    Thank you very much! Exactly what I was looking for. I just started using gradle and I’m amazed what I can do with it without much learning :-)

  4. Administrator Says:
    April 13th, 2010 at 21:25

    thanks JayHawk. there are other great resources. drop by at the gradle mailinglist or even the irc channel.

    cheers,
    rené

  5. Tomek Says:
    May 24th, 2010 at 21:07

    Hi Rene,

    thanks for the script, unfortunately it is not compatible with gradle-0.9-preview. :(

    First of all, you have to invoke it like:
    apply from: …
    instead of
    apply url: …
    from your build.gradle.

    The second thing is, that it doesn’t accept “apply id:’java’”. You should change it to “apply plugin: ‘java’” to make it work with 0.9-preview.


    Cheers
    Tomek

  6. Administrator Says:
    May 24th, 2010 at 21:10

    Hi Tomek, I’m actually using a nightly build version of gradle just some weeks before the 0.9-preview-1 was created. I should update it ;-)

    regards,
    René

  7. Tomek Says:
    May 24th, 2010 at 21:13

    Wow Rene, it took you 3 minutes to reply to my comment… scary… :D


    Tomek

  8. Rene Says:
    May 24th, 2010 at 21:14

    mere chance,
    never fear ;-)

    btw. How was the #geecon. unfurtunately I missed it.

  9. Tomek Says:
    May 24th, 2010 at 21:17

    I haven’t been there either. But the comments in polish blogosphere are rather good.


    Tomek

  10. Administrator Says:
    May 24th, 2010 at 21:26

    I hope a can attend there in 2011. already earmarked two dates for 2011: geecon and gr8conf

Comments