miranda

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é

25 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

  11. Andy Says:
    November 29th, 2010 at 16:54

    Hi, great article. Have you tried your script with a mutlimodule project? ant:iajc throws a compile exception for me as it cannot find its dependent project class files. I have configured the project as a dependency eg compile project(‘:someModule’).

    Only other difference i can think of is that my aspect is using annotations :-/

  12. Andy Says:
    November 29th, 2010 at 18:41

    Ok fixed it i think. I need to add ajc project(‘:someModule’) to my modules as well as compile project(‘:someModule’). I also had issues using annotations & source version 1.6 with aspectj 1.5.3 used in the example. Upgrading to 1.6.10 fixed this.

    Great plugin, thanks.

  13. Administrator Says:
    November 30th, 2010 at 23:33

    I have to admit, that I didn’t tried it yet with multimodule builds. As I see, you’ve got it running. great!

  14. camechis Says:
    March 7th, 2011 at 16:23

    I see Andy, that you managed to get this to work in a multi project environment. I am having trouble with this. It appears to ignore the project dependency and goes straight to the compile. It does however do the dependency after that fact. I have tried it with the default javaCompile and it does build the dep first. Not sure if anyone has any insight. I Have tried adding “ajc project(‘:someModule’)” to the dependencies to no avail.

    Thanks

  15. Administrator Says:
    March 7th, 2011 at 16:43

    Hi camechis,
    I’ll take a look into your problem as soon as my dev environment is back.

    regards,
    René

  16. camechis Says:
    March 7th, 2011 at 17:13

    thanks,

    On a note, I am not use the “apply :Url” this did not work. I have the tasked defined in the build file and I am using 1.0-M1 version of gradle.

  17. camechis Says:
    March 9th, 2011 at 16:05

    Any insight on this issue?

  18. René Says:
    March 10th, 2011 at 00:06

    Hi camechis,
    I took a look into the problem. I’ve updated the aspectj.gradle file to support project dependencies. can you have a look at this. I’ve committed it to github at https://github.com/breskeby/gradleplugins/blob/0.9-upgrade/aspectjPlugin/aspectJ.gradle

    the “apply url” is outdated. you can use “apply from:http:https://github.com/breskeby/gradleplugins/raw/0.9-upgrade/aspectjPlugin/aspectJ.gradle” in gradle-1.0-milestone-1

    regards,
    René

  19. camechis Says:
    March 11th, 2011 at 15:25

    I have tried the update. Its building my shared library now but it doesn’t look like its being included on the class path as I get unresolved symbols for class files included in the shared project. When I look in the shared project I don’t see a the jar file being created just the class files. Note ( the shared uses aspectj as well and builds fine on its own, and the other project builds fine if I manually build the shared first )

  20. camechis Says:
    March 11th, 2011 at 18:10

    I changed the line dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, “compileJava”) to

    dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, “jar”) and now it behaves correctly. Not sure if this is the correct fix or not.

  21. Lance Says:
    April 2nd, 2011 at 17:42

    I’ll second most of camechis’ comments.

    I had to make a couple of mods in order to get this to work for a multi project scenario with gradle 1.0-milestone-1

    1. on the ant.iajc invocation, I had to add a ‘forkclasspath’ attribute. I set this to: configurations.ajc.asPath,
    2. in each child project’s dependency secion, you need to refer to any other projects it depends on for both compile AND ajc. For example, if project foo depends on project bar, then you’d list the following in foo’s dependencies section:

    compile project(‘:bar’)
    ajc project(‘:bar’)

    3. in the overriden version of compileJava, as camechis says, you have to change which phase you’re referring to in the “dependsOn” statement (replace “compileJava” with “jar”).

    dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, “jar”)

    Also for the multi-project example shown here : https://github.com/breskeby/gradleplugins/tree/0.9-upgrade/aspectjPlugin/examples/MultiProject

    I would suggest changing the project names.

    In the absence of declared dependencies, I believe gradle executes the child projects in alphanumeric order (based on the name of the child project). Since “ProjectA” comes before “ProjectB”, Gradle will build it first anyway (even if you leave off a dependency declaration). I think this is why its working even though its left off item #2 above.

  22. ula.uvula Says:
    October 29th, 2011 at 09:31

    Hi,
    I feel as newbie in this big java eco cosmos.
    I worked alot with java+eclipse and a little bit with ant.
    In the meantime I have many project in my workspace (productive/examples/sandboxes etc).
    In the past each project has its own jar files in the project lib directory, but in
    the future I want to use a repository centric approach, so each project uses/shares the jars
    from a repository.

    As exercise I want to play around with AspectJ (AJDT in Ecplise) and
    started with Maven, but got some problems (but that is another story).
    So I switched to Gradle and found this article. Downloaded Gradle and your example (aspectJ.gradle).

    With this setup I have the following problem (maybe this is a understanding problem).
    – The project is created as “AspectJ Project” with AJDT in eclipse.
    I add classes and aspects and it works.
    – I add the build.gradle + aspectJ.grade to the project and use Gradle from CLI.
    – If I add some dependencies in the build.gradle (e.g. log4j),
    I run “gradle eclipse” to download the jars into the repo and update eclipse.
    – Back in eclipse I press a F5 for refesh.
    – Now the aspect file is “red” and can’t be compiled. (from the cli: gradle test is working well!).

    – I get the project back to working in eclipse with the following sequence:
    – Disable “build automatically” and clean the project.
    – Open the project properties and look at “Builders”
    – Here the list of “Java Builder” is growing every time I call gradle eclipse and one “Java Builder” is enabled
    – Disable the “Java Builder”
    – Enable “build automatically”
    – Project is working again from eclipse level

    BTW: – gradle’s compiles with the example from this article in directory “build”.
    – eclipse compiles the outputs into “bin” folder.

    If somebody can help me, it will be great.
    I also can export the eclipse project send send it by email, because, I haven’t seen
    how to attach a file to this blog.

    Uwe

  23. breskeby Says:
    October 29th, 2011 at 09:51

    Hi ula, you can send me your sample project at gradle@breskeby.com. Currently I am on travel, so it might take some days to reply an answer. another great place to post your questions about gradle is http://forums.gradle.org. But don’t hesitate to post you answers here.
    some further information would be great:

    - Which eclipse ajdt version do you use?
    - which gradle version do you use?

  24. ula.uvula Says:
    October 29th, 2011 at 10:43

    Hi, thanks for the quick response.
    Can we use german?

    Envirnment (every thing new)
    - Windows 7 64bit
    - Eclipse Indigo (3.7 SR1)
    - AJDT 2.1.3 (AspectJ 1.6.12) from the developer url (http://download.eclipse.org/tools/ajdt/37/dev/update)
    Problem also exists with the “stable release”
    - Gradle 1.0-milestone-5 (Path variable extended to bin folder, no other gradle environment variable set).

    Eclipse project created: “New->Project->AspectJ->AspectJ Project”
    Eclipse project exported „General->File System“ and zipped, attached to e-mail

  25. Kiran Says:
    November 11th, 2011 at 13:53

    Hi breskeby!

    I am using your aspectj plugin to compile my code.

    Below is the code.

    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
    apply plugin:'java'
    apply plugin: 'osgi'

    sourceSets {
        main {
            java {
                srcDir 'src/main'
            }
        }
    }


    dependencies{
        compile group: 'org.springframework', name: 'spring-beans', version: '3.0.0.RELEASE', transitive: false
        compile group: 'log4j', name: 'log4j', version: '1.2.15', transitive: false
        compile group: 'org.springframework', name: 'spring-context', version: '3.0.0.RELEASE', transitive: false
        compile group: 'org.springframework', name: 'spring-asm', version: '3.0.0.RELEASE', transitive: false
        compile group: 'org.springframework', name: 'spring-expression', version: '3.0.0.RELEASE', transitive: false
        compile group: 'org.springframework', name: 'spring-aop', version: '3.0.0.RELEASE', transitive: false  
    //  compile group: 'org.aspectj', name: 'com.springsource.org.aspectj.weaver', version: '1.6.5.RELEASE', transitive: false
        compile group: 'com.google.guava', name: 'guava', version: 'r05', transitive: false
        compile group: 'org.springframework', name: 'spring-test', version: '3.0.0.RELEASE', transitive: false
        compile group: 'org.springframework', name: 'spring-core', version: '3.0.0.RELEASE', transitive: false
       
        ajc group: 'org.aspectj', name: 'aspectjtools', version: '1.6.5', transitive: false
        compile group: 'aspectj', name: 'aspectjrt', version: '1.5.3', transitive: false
        testCompile group: 'junit', name: 'junit', version: '4.7', transitive: false
        testCompile group: 'org.mockito', name: 'mockito-all', version: '1.8.2', transitive: false
    }

    when i compile without aspectj it is compiling without any errors. but when i apply aspectj plugin it is giving below errors.

    [ant:iajc] D:\src\main\com\\config\common\Factory.java:47 [error] The method setApplicationContext(ApplicationContext) of type Factory must override a superclass method
    [ant:iajc] public void setApplicationContext(ApplicationContext arg0) throws BeansException {
    [ant:iajc] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    but i can see setApplicationContext is present in spring-context jar.

    Can you help me in this regard

Comments