Post

AOP and AspectJ Part 2

In previous post, we discussed the concept of AOP and explained the terminologies used in AspectJ. Now we can use some examples to show how AspectJ can be used in a project.

Overview

In this example, we will build a trace log aspect. The idea is, for any method that meets certain requirement (the pointcut we defined), we print a log before and after the method. For instance, we define an annotation @TraceLog and a pointcut that matches any method annotated with it. Then for the following method:

1
2
3
4
5
6
@TraceLog
int sum(int a, int b) {
    int s = a + b;
    LOGGER.info("The sum is {}", s);
    return s;
}

After executing sum(41, 42), we should see the following logs:

1
2
3
Calling sum(41,42)
The sum is 83
Method call sum(41,42) returns 83

The first and the last logs are printed by our apsect. Next we will see how we can achieve this goal.

Define the annotation

First we define the annotation we use on the method we want to trace. For how to define a custom annotation, see Java Annotation.

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TraceLog {
}

Define the pointcut

Next We define a pointcut that matches methods that are annotated with @TraceLog. Remember that we have to use the full name, including the full package, of the annotation. It might be more flexible if we can use TraceLog.class.getName(). However, annotation value must be a constant.

1
2
3
4
5
6
public class TraceLogPointcut {

    @Pointcut("execution(@com.hechaol.example.aspectj.annotation.TraceLog * *(..))")
    public void traceLog() {
    }
}

This pointcut matches methods annotated with @TraceLog and having any return type, any name as well as any parameters.

Define the aspect

Now we define the aspect. Because we want to print logs both before and after the method execution, we need to define an around aspect. We can also define two aspects - one before and one after. But using one around should be simpler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
public class TraceLogAspect {

    @Around("com.hechaol.example.aspectj.pointcut.TraceLogPointcut.traceLog()")
    public Object aroundMethodCall(ProceedingJoinPoint joinPoint)
        throws Throwable {
        Method method =
            ((MethodSignature) joinPoint.getSignature()).getMethod();
        String methodName = method.getName();
        String parameters = Arrays.toString(joinPoint.getArgs());
        System.out.println("Calling " + methodName + "(" + parameters + ")");
        Object returnValue;
        try {
            returnValue = joinPoint.proceed();
        } catch (Throwable throwable) {
            System.out.println("Method call "
                + methodName + "(" + parameters + ") throws: " + throwable);
            throw throwable;
        }
        System.out.println("Method call "
            + methodName + "(" + parameters + ") returns: " + returnValue);
        return returnValue;
    }
}

The aspect just hooks the method and print logs before and after execution.

Define the business logic

Finally, we can define our business logic. Here we write a simple calculator.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Calculator {

    @TraceLog
    public static int add(int a, int b) {
        return a + b;
    }

    @TraceLog
    public static int sub(int a, int b) {
        return a - b;
    }

    @TraceLog
    public static int multiply(int a, int b) {
        return a * b;
    }

    @TraceLog
    public static int divide(int a, int b) {
        return a / b;
    }
}

Weave It

Remember we need to weave the aspect code to our business code? In this example because we have the source code, we can just use compile time weaving. We need to add a weaving plugin to maven.

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
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.7</version>
    <configuration>
        <complianceLevel>${java.version}</complianceLevel>
        <source>${maven.compiler.source}</source>
        <target>${maven.compiler.target}</target>
        <showWeaveInfo>true</showWeaveInfo>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>test-compile</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
</plugin>

Run It

We can write a main class to run the code and see what is printed. For each log we print, we can add a prefix to distinguish it from the log the aspect prints.

1
2
3
4
5
6
7
8
9
10
11
public class Main {
    public static void main(String[] args) {
        int sum = Calculator.add(41, 42);
        System.out.println("====== The sum is: " + sum);

        System.out.println();

        int quotient = Calculator.divide(42, 0);
        System.out.println("====== The quotient is: " + quotient);
    }
}

After running, we should see the result as follows:

1
2
3
4
5
6
7
Calling add([41, 42])
Method call add([41, 42]) returns: 83
====== The sum is: 83

Calling divide([42, 0])
Method call divide([42, 0]) throws: java.lang.ArithmeticException: / by zero
====== Got exception: java.lang.ArithmeticException: / by zero

Conclusion

In this post, we use an trace log example to show how AspectJ can be used in a project. The entire project of this example is on github.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.