Search code examples
javagradleaspectjaspectfreefair-aspectj

How to make an AspectJ Aspect work in a Gradle project?


I am using the following sample code to understand AspectJ:

public class Account {
    int balance = 20;

    public boolean withdraw(int amount) {
        if (balance < amount) {
            return false;
        }
        balance = balance - amount;
        return true;
    }

    public static void main(String[] args) {
        Account acc = new Account();
        acc.withdraw(5);

        Account acc2 = new Account();
        acc2.withdraw(25);
    }
}

and the following Aspect:

public aspect AccountAspect {

    pointcut callWithDraw(int amount, Account acc) :
            call(boolean Account.withdraw(int)) && args(amount) && target(acc);

    before(int amount, Account acc) : callWithDraw(amount, acc) {
        System.out.printf("[before] withDraw, current balance %d%n", acc.balance);
    }

    boolean around(int amount, Account acc) : callWithDraw(amount, acc) {
        if (acc.balance < amount) {
            System.out.println("[around] withDraw, check failed");
            return false;
        }
        System.out.println("[around] withDraw, check success");
        return proceed(amount, acc);
    }

    after(int amount, Account acc) : callWithDraw(amount, acc) {
        System.out.printf("[after] withDraw, current balance %d%n", acc.balance);
    }
}

I am using the following Gradle build file:

plugins {
    id 'java'
    id "io.freefair.aspectj.post-compile-weaving" version "4.1.4"
}

configurations {
    ajc
    aspects
    aspectCompile
    compile{
        extendsFrom aspects
    }
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation group: 'org.aspectj', name: 'aspectjrt', version: '1.9.4'
    implementation group: 'org.aspectj', name: 'aspectjweaver', version: '1.9.4'
    implementation group: 'org.codehaus.mojo', name: 'aspectj-maven-plugin', version: '1.8'
    testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileJava {
    sourceCompatibility = "1.8"
    targetCompatibility = "1.8"
    //The following two lines are useful if you have queryDSL if not ignore
    dependsOn configurations.ajc.getTaskDependencyFromProjectDependency(true, "compileJava")
}

and I am using Ajc compiler in Intellij to compile Java classes and aspects. In addition, I am setting the following VM options:

-javaagent:c:\\Users\\dev\\extlibs\\aspectjweaver-1.9.6.jar

The code compiles and runs without any issues but the aspect never triggers. I am not sure what I am missing in my configuration but I could not find anything else I can do when using AspectJ. If I use @Aspect annotation and register the annotated Aspect in META-INF/aop.xml, the aspect runs but annotated @Aspect Java classes does not allow to create privileged aspect which I need to catch/intercept a private method. Any suggestions on how to resolve this issue?


Solution

  • You are mixing a whole lot of stuff there:

    • Freefair post-compile-time weaving plugin: This is unnecessary because you have Java source code and can compile it together with the aspect using normal compile-time weaving.
    • AspectJ Maven plugin: This is useless because you cannot simply use a Maven plugin in Gradle.
    • AspectJ load-time weaving Java agent: This is unnecessary because, like I said, you can use normal compile-time weaving here. You also do not need aop.xml if you do not use -javaagent:.

    I guess you could use all three options, compile-time, post-compile-time and load-time weaving, but you should decide which one and not mix them. You also should decide if you want to use Gradle or Maven and also not try to mix them.

    This is how compile-time weaving works:

    1. Put both the application and aspect code in directory src/main/aspectj.
    2. Simplify your Gradle build file to:
    plugins {
      id "java"
      id "io.freefair.aspectj" version "5.1.1"
    }
    
    group "org.example"
    version "1.0-SNAPSHOT"
    
    repositories {
      mavenCentral()
    }
    
    dependencies {
      implementation "org.aspectj:aspectjrt:1.9.6"
    }
    
    1. Now when running the main class from my IDE, I see something like:
    > Task :compileJava NO-SOURCE
    > Task :compileAspectj UP-TO-DATE
    > Task :processResources NO-SOURCE
    > Task :classes UP-TO-DATE
    
    > Task :Account.main()
    [before] withDraw, current balance 20
    [around] withDraw, check success
    [after] withDraw, current balance 15
    [before] withDraw, current balance 20
    [around] withDraw, check failed
    [after] withDraw, current balance 20
    

    Of course you could also compile a separate aspect library and weave it into your application code, if the aspect library is to be re-used for several applications or modules or if the application source code is not written in Java (but e.g. in Kotlin, Groovy or Scala) and hence cannot compiled by the AspectJ compiler directly. I am not going into this here, though, because I wanted to show you the simplest solution, as you seem to be a beginner. (BTW, I am not a Gradle user either, I normally use AspectJ with Maven.)


    Update: The plugin documentation is really bad, but you can see some example projects in the maintainer's GitHub repository:

    • aspect: compile-time weaving, i.e. application + aspect in one module, as shown here in my answer
    • httpcore-nio: post-compile-time weaving into a 3rd party library
    • test: how to test aspects
    • weaving: mixed-language aspects in Java, Groovy and Lombok and how to use them all together in a single module

    All of this can also be done using Maven and AspectJ Maven Plugin, but you specifically asked for Gradle.