Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does AspectJ @Around advice execute twice?

Tags:

java

aop

aspectj

I have the following AspectJ example that I've done as a "hello world" style proof of concept. The advising code in the StyleAspect seems to execute twice even though the actual code in SomeClass only executes once (as required).

Here's the code:

Firstly, an annotation called WithStyle:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface WithStyle {  
}

Then, an aspect that intercepts any code with the @WithStyle annotation

@Aspect
public class StyleAspect {

    @Around("@annotation(WithStyle)")
    public Object doItWithStyle(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("Doing it in style...");
        Object result = pjp.proceed();
        System.out.println("Done");
        
        return result;
    }
}

and finally, some code with the annotation

public class SomeClass {
    
    @WithStyle
    public void doIt() {
        System.out.println("I'm doing it....");
    }
}

When I run this, I get the following output:

--- exec-maven-plugin:1.2.1:exec (default-cli) @ AspectJTest ---
Doing it in style...
Doing it in style...
I'm doing it....
Done
Done

So it seems as if while the code itself only executes once, the code in the aspect is getting executed twice.

Here's the calling code:

public class Main {
    
    public static void main(String[] args) {
        SomeClass someClass = new SomeClass();
        someClass.doIt();
    }
}

and for completeness, I'm including the pom with the AspectJ plugin config

<?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>ie.philb</groupId>
    <artifactId>AspectJTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.java.target>1.8</project.build.java.target>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>aspectj-maven-plugin</artifactId>
                <version>1.11</version>
                <configuration>
                    <complianceLevel>1.8</complianceLevel>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>       <!-- use this goal to weave all your main classes -->
                            <goal>test-compile</goal>  <!-- use this goal to weave all your test classes -->
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
      
</project>
like image 504
PhilDin Avatar asked Nov 23 '20 13:11

PhilDin


People also ask

Why annotations should be executed twice?

Using only annotations creates another problem that we don't need to think about while using patterns; It will make our advice run twice(or more), because the annotation pointcut don't specify if it should be run during execution or initialization.

What is advice in AspectJ?

AspectJ supports three kinds of advice. The kind of advice determines how it interacts with the join points it is defined over. Thus AspectJ divides advice into that which runs before its join points, that which runs after its join points, and that which runs in place of (or "around") its join points.

Which advice is executed once a joint point finishes?

Which advice is executed once a joint point finishes? Explanation: An after advice is executed after a join point finishes, whenever it returns a result or throws an exception abnormally.

What is a pointcut a join point an advice an Aspect weaving?

Pointcut: Pointcut is expressions that are matched with join points to determine whether advice needs to be executed or not. Pointcut uses different kinds of expressions that are matched with the join points and Spring framework uses the AspectJ pointcut expression language.


1 Answers

Your around() advice is intercepting both the call and execution join points of the method annotated with @WithStyle (i.e., doIt()). If you add a System.out.println(pjp); to your aspect:

@Aspect
public class StyleAspect {

    @Around("@annotation(WithStyle) ") 
    public Object doItWithStyle(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(pjp);
        System.out.println("Doing it in style...");
        Object result;
        try{
            result = pjp.proceed();
        }
        finally{
            System.out.println("Done");
        }
        return result;
    }
}

you would get the following output:

call(public void SomeClass.doIt()) <----
Doing it in style...
execution(public void SomeClass.doIt()) <----
Doing it in style...
I'm doing it....
Done
Done

You can clearly see that the join points call and execution of method SomeClass.doIt() are being intercepted by the around advice doItWithStyle.

From the interception of the call, the around advice is weaving the code as follows:

// around advice code  before the pjp.proceed();
someClass.doIt(); // during the pjp.proceed();
// around advice code  after the pjp.proceed();

consequently:

 System.out.println("Doing it in style...");.
 someClass.doIt();
 System.out.println("Done");

From the execution:

@WithStyle
public void doIt() {
    // around advice code  before the pjp.proceed();
    System.out.println("I'm doing it....");
  // around advice code  after the pjp.proceed();
}

consequently:

@WithStyle
public void doIt() {
    System.out.println("Doing it in style...");
    System.out.println("I'm doing it....");
    System.out.println("Done");
}

resulting in the output:

Doing it in style... 
Doing it in style...
I'm doing it....
Done
Done

Now, if you want to avoid the around advice from intercepting both the call and the execution of the method doIt(). You need to further restrict the join points intercepted by your around advice. To just intercept the method call, you can do:

 @Around("@annotation(WithStyle) && call(* *(..))") 

for the method execution:

@Around("@annotation(WithStyle) && execution(* *(..))") 

You can further restrict the join points being intercepted based on the number of arguments of the method, its returning type, name, and so on, by tuning the signature of the call or execution pointcuts.

like image 171
dreamcrash Avatar answered Oct 24 '22 13:10

dreamcrash