9

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?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
F. K.
  • 694
  • 3
  • 9
  • 23

2 Answers2

14

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.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thanks @kriegaex for the thorough answer. It makes a lot of sense now. I have been going through lots of posts and examples of AOP and Aspectj and none come come close to what you posted on here. – F. K. Mar 04 '21 at 13:02
  • Hi Kriegaex, I simplified the build.gradle to the one you posted. I put both aspect and Account in src/main/java/aspectj and still Aspect did not seem to kick in. I tried also putting them into their own package com.abc.demo but still Aspect did not trigger. One thing to add here is I have to use Gradle since this is the standard here. – F. K. Mar 04 '21 at 13:17
  • I said `src/main/aspectj`, not `src/main/java/aspectj`. – kriegaex Mar 04 '21 at 16:09
  • Ok. This worked but is there a way, this can be in a java package rather than in src/main/aspecj? or it must exist in src/main/aspectj? The reason I am asking is that this is a big Java project under src/main/java and all our source code is in our root package. – F. K. Mar 04 '21 at 18:24
  • 3
    Kriegaex, I see now why you said, this must be in src/main/aspectj since this is the default location where the freefair plugin looks for aspects. I am able to make it work in src/main/java by setting the following properties in build.gradle: sourceSets.main.aspectj.srcDir "src/main/java" sourceSets.main.java.srcDirs = files() Thanks a lot for your help. – F. K. Mar 04 '21 at 19:42
  • Yes, I just used the defaults. Actually, I was happy to get a sample project running in the first place. Like I said, I have zero Gradle experience and only was curious because the question was about AspectJ, which is rather my sweet spot. – kriegaex Mar 05 '21 at 01:49
2

Thanks to the pointers in this thread, I was able to get 2 flavors of writing aspects to be built successfully from Gradle -

  1. aspects defined inside src/main/java - https://github.com/vadakr/gradle-aspect-inside-java
  2. aspects defined outside src/main/java, for example in src/main/aspectj - https://github.com/vadakr/gradle-aspect-outside-java

Note that I've used the 'io.freefair.aspectj.post-compile-weaving' Gradle plugin (https://docs.freefair.io/gradle-plugins/6.0.0-m2/reference/#_io_freefair_aspectj_post_compile_weaving) becaause it was the simplest in that it automatically passed Java classes (all possible join points) into 'ajc' - all you needed to do was point it to the aspects themselves.

When debugging the Gradle build process, looking at the generated ajc options proved to be very useful. Cross-checking these against https://www.eclipse.org/aspectj/doc/next/devguide/ajc-ref.html usually pointed to the source of the problem. It was usually -inpath or -sourceroots related.

  • -inpath is how you get your join points into 'ajc'
  • -sourceroots is how you get your aspects (pointcuts/advice) into 'ajc'

Note that the screenshot is from IntelliJ.