1

I am trying to build a POC project using AspectJ without using Spring AOP. I am using an annotation based approach where I want to run the aspect @Around the method which has been annotated with an annotation. For some reason my aspects don't get triggered. Below is my code:

pom.xml

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.9</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.7</version>
            <configuration>
                <complianceLevel>1.8</complianceLevel>
                <source>1.8</source>
                <target>1.8</target>
                <showWeaveInfo>true</showWeaveInfo>
                <verbose>true</verbose>
                <Xlint>ignore</Xlint>
                <encoding>UTF-8</encoding>
            </configuration>
            <executions>
                <execution>
                    <goals>
                        <!-- use this goal to weave all your main classes -->
                        <goal>compile</goal>
                        <!-- use this goal to weave all your test classes -->
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

resources/META-INF/aop.xml

<aspectj>
    <aspects>
        <aspect name="com.aditya.personal.aspects.MetricsAspect"/>
        <weaver options="-verbose -showWeaveInfo">
            <include within="com.carrot.personal.aspects.*"/>
        </weaver>
    </aspects>
</aspectj>

My Aspect:

@Aspect
public class DataTrackAspect {

    @Around("@annotation(com.carrot.personal.aspects.DataTrackEnabled)")
    public Object performAspect(ProceedingJoinPoint joinPoint, DataTrackEnabled dataTrackEnabled) throws Throwable {

        Object result = joinPoint.proceed();
        DataTrackHelper dataTrackHelper = new DataTrackHelper();
        dataTrackHelper.recordInstanceCount(dataTrackEnabled.dataClass(), dataTrackEnabled.dataName(), dataTrackEnabled.instance());
        return result;
    }
}

The annotated method

@DataTrackEnabled(dataClass = "Hey", dataName = "There", instance = "How are you?")
public Map<String, String> fetchUser() {
    Map<String, String> returnable = new HashMap<>();
    returnable.put("firstName", "carrot");
    returnable.put("lastName", "radish");
    return returnable;
}

I can't seem to figure it out as to what am I missing. I have uploaded the sample code on GitHub here.

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
iam.Carrot
  • 4,976
  • 2
  • 24
  • 71
  • It looks like you may not have your POM set up correctly. There are three ways to configure it - compile-time, post-compile, and runtime, and it looks like you may have configured pieces of compile and post-compile configuration. I'm answering as a comment because I don't want to repeat all of the code here in an answer, but I recommend looking at a tutorial such as https://www.baeldung.com/aspectj to get the correct POM configuration. – GreyBeardedGeek Mar 21 '21 at 16:05
  • @GreyBeardedGeek I was following the same article. I copied over the pom configuration from [this section](https://www.baeldung.com/aspectj#compile-time-weaving). Using compile time weaving. But I could not get it to work and in the sample code that has been linked to GitHub, they're using `Spring-AOP`, hence the question – iam.Carrot Mar 21 '21 at 16:25

1 Answers1

2

You are using AspectJ Maven Plugin, i.e. you are going to use compile-time weaving. Therefore, you do not need aop.xml because that one is used for load-time weaving. So you can delete it.

During compilation you should get a compile error:

Unbound pointcut parameter 'dataTrackEnabled'

That tells you that your advice method has a parameter dataTrackEnabled which does not occur anywhere in the pointcut as it should. So just change the pointcut from

@Around("@annotation(com.carrot.personal.aspects.DataTrackEnabled)")

to

@Around("@annotation(dataTrackEnabled)")

As you see, I am referring to the method parameter name from the pointcut. If you would not bind the annotation to a parameter, then you would need the fully qualified class name as you used it before.

Also, your Maven POM should cause this error:

diamond operator is not supported in -source 1.5

This is because using AspectJ Maven does not automatically deactivate or replace Maven Compiler Plugin and the latter complains that you did not set source and target versions for it. So you have two options:

  1. Deactivate Maven Compiler, let AspectJ Maven compile your Java and aspect classes.
  2. Set the compiler level to 1.8 (or simply 8) for Maven Compiler and use the same settings for AspectJ Maven.

I will choose option no. 2 in this case. Maven knows properties named maven.compiler.source and maven.compiler.target. Let us define and use them:

  <!-- (...) -->

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
  </properties>

  <!-- (...) -->

        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
          <complianceLevel>${maven.compiler.target}</complianceLevel>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>

  <!-- (...) -->

Now there are a few more warnings:

Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!

File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!

bad version number found in C:\Users\alexa\.m2\repository\org\aspectj\aspectjrt\1.8.9\aspectjrt-1.8.9.jar expected 1.8.2 found 1.8.9

The first two are just because you forgot to set <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>. While you are at it, you can set the encoding for generated reports, too: <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>.

The last one is aspect-related and due to the fact that AspectJ Maven 1.7 depends on AspectJ 1.8.2, not 1.8.9. But be careful! It depends on aspectjtools (for using the compiler), not on aspectjweaver (for load-time weaving). You can either upgrade the version or override the dependency inside the plugin. I am going to show you both at the same time, just in case.

Last but not least, the latest version of AspectJ 1.8 is 1.8.13. But you can just use the very latest version 1.9.6, it supports up to Java 14 (the next version for Java 15+16 is almost ready) and can still produce byte code for Java 8.

How about this POM?

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.carrot</groupId>
  <artifactId>SO_AJ_MavenProjectNotWorking_66734262</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <aspectj.version>1.9.6</aspectj.version>
  </properties>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
          <complianceLevel>${maven.compiler.target}</complianceLevel>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.target}</target>
          <showWeaveInfo>true</showWeaveInfo>
          <verbose>true</verbose>
          <Xlint>ignore</Xlint>
          <encoding>UTF-8</encoding>
        </configuration>
        <executions>
          <execution>
            <goals>
              <!-- use this goal to weave all your main classes -->
              <goal>compile</goal>
              <!-- use this goal to weave all your test classes -->
              <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>${aspectj.version}</version>
    </dependency>
  </dependencies>

</project>

Now when you run the program, you should see something like:

Recording: dataClass = Hey, dataName = There, instance = How are you?
Recording: dataClass = Hey, dataName = There, instance = How are you?

You may ask why the advice is triggered twice. Well, just because @annotation(dataTrackEnabled) captures both the method call() and the method execution() pointcut. So let us limit the match to just executions.

The full solution looks like this (do not forget RUNTIME retention for your annotation):

package com.carrot.personal.aspects;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface DataTrackEnabled {
  String dataClass();
  String dataName();
  String instance();
}
package com.carrot.personal.app;

public class DataTrackHelper {
  public void recordInstanceCount(String dataClass, String dataName, String instance) {
    System.out.println("Recording: dataClass = " + dataClass + ", dataName = " + dataName + ", instance = " + instance);
  }
}
package com.carrot.personal.app;

import com.carrot.personal.aspects.DataTrackEnabled;

import java.util.HashMap;
import java.util.Map;

public class Application {
  public static void main(String[] args) {
    System.out.println(new Application().fetchUser());
  }

  @DataTrackEnabled(dataClass = "Hey", dataName = "There", instance = "How are you?")
  public Map<String, String> fetchUser() {
    Map<String, String> returnable = new HashMap<>();
    returnable.put("firstName", "carrot");
    returnable.put("lastName", "radish");
    return returnable;
  }
}
package com.carrot.personal.aspects;

import com.carrot.personal.app.DataTrackHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class DataTrackAspect {
  @Around("@annotation(dataTrackEnabled) && execution(* *(..))")
  public Object performAspect(ProceedingJoinPoint joinPoint, DataTrackEnabled dataTrackEnabled) throws Throwable {
    System.out.println(joinPoint);
    Object result = joinPoint.proceed();
    DataTrackHelper dataTrackHelper = new DataTrackHelper();
    dataTrackHelper.recordInstanceCount(dataTrackEnabled.dataClass(), dataTrackEnabled.dataName(), dataTrackEnabled.instance());
    return result;
  }
}

I added some more log output, so let us run the application again:

execution(Map com.carrot.personal.app.Application.fetchUser())
Recording: dataClass = Hey, dataName = There, instance = How are you?
{firstName=carrot, lastName=radish}
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Thank you for the detailed explanation and modified code (+1). I just have two questions: (1) What does the `&& execution(* *(..)` do? Where can I read more about it? (2) When I run the code in my IDE (Intellij) it does not trigger the aspect but when I first run `maven clean compile` and then run in my IDE, then aspect gets triggered. Should I be using a different weaving for it to run without running `maven clean compile`? – iam.Carrot Mar 24 '21 at 20:41
  • (1) You want to use AspectJ. So why don't you read the AspectJ manual? The short answer is: `call()` intercepts the source of a call, `execution()` the target. Imagine a method called from 50 different places in your code. `call()` would weave into each othe the calling classes, `execution()` only into the class where the method is defined. For a more in-depth explanation, see [my answer here](https://stackoverflow.com/a/18149106/1082681). But do read the manual too! (2) Sounds as if you did not install the AspectJ extension for IntelliJ. – kriegaex Mar 25 '21 at 13:04