Aspect Oriented Programming,  Java,  Spring Framework

Spring Aspect Oriented Programming

Aspecting in Spring refers to re-usable blocks of code that are injected into the application at runtime and can be a powerful tool for modularizing and adding behaviour to solve cross-cutting concerns such as:

  • Security
  • Logging
  • Transaction Management
  • Caching

Core concepts

  • Aspect
  • Join point
  • Advice
  • Pointcut
  • Weaving

The modularization of a cross-cutting concern that is implemented in regular classes either through a schema-based xml approach or by annotating the class @Aspect.

A join point a specific point in the application execution such as when a method executes, exception handling, changing object variable values, etc.

An advice is a simple Java method with one of the advice annotations and represents an action taken by an aspect at a particular join point.

Advice annotations:

The @Before advice is executed before the join point in the application is reached.
The @After advice is executed after the join point in the application is reached.
The @AfterReturning advice is executed after the join point is reached and the method returns normally.
The @AfterThrowing advice is executed after the join point is reached and the method throws an exception.
The @Around advice is executed before and after the join point is reached.

A pointcut is an expression that looks for types and objects on which to apply the aspect’s advices.

The @Pointcut annotation defines a pointcut expression that can be used by advice annotations.

Weaving is the process of linking aspects with other application types or objects to create an advised object. This is a key concept in aspect-oriented programming (AOP), where it allows the aspect’s code to be integrated with the main application code at various points (join points). Weaving can occur at compile time, load time, or runtime.

Spring AOP & AspectJ

To support AOP with annotations, Spring uses AspectJ annotations. AspectJ is a framework that provides a declarative language for defining aspects and weaving them into the application code as well as for pointcut parsing and matching.
Although AOP with annotations is supported by Spring, it is not a Spring-specific feature. It is a feature of AspectJ that Spring supports so Spring AOP is not dependent on the AspectJ compiler or weaver.

To enable annotation support in the Spring IoC container, add the @EnableAspectJAutoProxy annotation to a configuration class.
To apply AOP, Spring creates a proxy object that wraps the bean to which the aspect is applied and by default uses JDK dynamic proxies that are based on interfaces.
For scenarios where interfaces are not available, use CGLIB proxies and set the proxyTargetClass attribute of the @EnableAspectJAutoProxy annotation to true.

Advice examples

package com.spring.aspects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Component
public class LoggingAspect {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Before("execution(* *.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        var name = joinPoint.getSignature().getName();
        var args = Arrays.toString(joinPoint.getArgs());
        log.info("The method {}() begins with {}", name, args);
    }

    @After("execution(* *.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        var name = joinPoint.getSignature().getName();
        log.info("The method {}() ends", name);
    }
    
    @AfterReturning("execution(* *.*(..))")
    public void logAfterReturning(JoinPoint joinPoint) {
        var name = joinPoint.getSignature().getName();
        log.info("The method {}() ends", name);
    }
    
    //Executed only after there is an exception thrown by the join point method

    @AfterThrowing("execution(* *.*(..))")
    public void logAfterThrowing(JoinPoint joinPoint) {
        var name = joinPoint.getSignature().getName();
        log.info("An exception has been thrown in {}()", name);
    }
    
    //The exception thrown by the join point can be accessed by adding an argument of type Exception to the advice method
    //The type Throwable is a superclass of all errors and exceptions in Java
    //The following advice will catch all exceptions thrown by the join points

    @AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        var name = joinPoint.getSignature().getName();
        log.info("An exception {} has been thrown in {}()", ex, name);
    }

    
    //OR
    //If you are interested in only 1 type of exception, you can specify the type in the advice method
    //This advice will only execute when exceptions of a compatible type are thrown

    @AfterThrowing(pointcut = "execution(* *.*(..))", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, IllegalArgumentException ex) {
        var args = Arrays.toString(joinPoint.getArgs());
        var name = joinPoint.getSignature().getName();
        log.info("An exception {} has been thrown in {}()", args, name);
    }


    //The @Around advice is the most powerful advice
    //It can be used to implement method caching, security, logging, etc.
    //It gains full control of a join point so you can combine all the other advices into one
    //You can control when to proceed to the join point and when to skip it

    @Around("execution(* *.*(..))")
    public void logAround(ProceedingJoinPoint joinPoint) throws Throwable{
        var name = joinPoint.getSignature().getName();
        log.info("The method {}() begins with {}", name, Arrays.toString(joinPoint.getArgs()));
        try {
            joinPoint.proceed();
        } catch (IllegalArgumentException e) {
            log.error("Illegal argument {} in {}()", Arrays.toString(joinPoint.getArgs()), name);
            throw e;
        }
        log.info("The method {}() ends", name);
    }
}

Defining pointcuts

The pointcut syntax:

designator("r p.c.m(arg)")
SyntaxDescription
designatorexecution – expression for matching method execution
within – expressions for matching within certain types
target – expressions for matching a certain type
@annotation – expressions for matching specific annotations
rReturn type
pPackage
cClass
mMethod
argArguments

Accessing joinpoint information

Advices are applied at different joinpoints and for an advice to take the correct action, it requires detailed information about the join point/s.

An advice can access the join point information by declaring an argument of type org.aspectj.lang.JoinPoint in the advice method signature

@Before("execution(* *.*(..))")
public void logJointPoint(JoinPoint joinPoint) {
  log.info("Joint point {}", joinPoint.getKind());
  log.info("Signature declaring type {}", joinPoint.getSignature().getDeclaringTypeName());
  log.info("Signature name {}", joinPoint.getSignature().getName());
  log.info("Arguments {}", Arrays.toString(joinPoint.getArgs()));
  log.info("Target class {}", joinPoint.getTarget().getClass().getName());
  log.info("This class {}", joinPoint.getThis().getClass().getName());
}

Reusing the pointcut expression

You can use the @Pointcut annotation to define a pointcut independently to be reused in multiple advices.

In an aspect, a pointcut can be declared as a simple method with the @Pointcut annotation. Advices can refer to this pointcut by the method name.

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* *.**(..)")
    private void loggingOperation() {}

    @Before("loggingOperation()")
    public void logBefore(JoinPoint joinPoint) {...}

    @After("loggingOperation()")
    public void logAfter(JointPoint joinPoint) {...} 

    @AfterReturning(
             pointcut = "loggingOperation()",
             returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {..}

    @AfterThrowing(
             pointcut = "loggingOperation()",
             throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, IllegalArgumentException ex) {..}

}

Specifying precedence

To specify precedence, either make aspects implement the Ordered interface or use the @Order annotation.

Ordered interface:

The lower value returned by the get Order method represents higher priority.

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.core.Ordered;

@Aspect
@Component
public class ValidationAspect implements Ordered {
    
    @Override
    public int getOrder() {
        return 0;
    }

@Order annotation:

The order number should be defined in the @Order annotation.

@Aspect
@Component
@Order(0)
public class ValidationAspect {
}