Aspect Oriented Programming

AspectJ is a Java library that implements aspect-oriented programming. Aspect-Oriented Programming (AOP) is a programming paradigm that allows the separation of cross-cutting concerns (e.g. logging, security, or error handling) from the main business logic. It achieves this by defining aspects that can be applied to different parts of a program.

Benefits of AOP:

  1. Improved modularity: Cross-cutting concerns are isolated into separate aspects.
  2. Reusability: Aspects can be reused across different parts of the application.
  3. Easier maintenance: Changes to concerns like logging or security can be made in one place rather than throughout the codebase.

Example

pom.xml

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

A crosscutting annotation is typically used in AOP to define behaviors that apply across multiple parts of a program.

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


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface YourAnnotation {
}
public class YourClass {

    public static void main(String[] args) {
        YourClass yourClass = new YourClass();
        yourClass.yourMethodAround();
    }

    @YourAnnotation
    public void yourMethodAround(){
        System.out.println("Executing TestTarget.yourMethodAround()");
    }
}

Core Implementation

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;

@Aspect
public class YourAspect {

    // Defines a pointcut that we can use in the @Before, @After, @AfterThrowing, @AfterReturning, @Around specifications
    // The pointcut will look for the @YourAnnotation
    @Pointcut("@annotation(YourAnnotation)")
    public void annotationPointCutDefinition(){
    }

    //Defines a pointcut that we can use in the @Before,@After, @AfterThrowing, @AfterReturning,@Around specifications
    //The pointcut is a catch-all pointcut with the scope of execution
    @Pointcut("execution(* *(..))")
    public void atExecution(){}

    //Defines a pointcut where the @YourAnnotation exists
    //and combines that with a catch-all pointcut with the scope of execution
    @Around("@annotation(YourAnnotation) && execution(* *(..))")

    //ProceedingJointPoint = the reference of the call to the method.
    //The difference between ProceedingJointPoint and JointPoint is that a JointPoint can't be continued (proceeded)
    //A ProceedingJointPoint can be continued (proceeded) and is needed for an Around advice
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // Default Object that we can use to return to the consumer
        Object returnObject = null;
        try {
            System.out.println("YourAspect's aroundAdvice's body is now executed Before yourMethodAround is called.");
            // We choose to continue the call to the method in question
            returnObject = joinPoint.proceed();
            // If no exception is thrown we should land here and we can modify the returnObject, if we want to.
        } catch (Throwable throwable) {
            // Here we can catch and modify any exceptions that are called
            // We could potentially not throw the exception to the caller and instead return "null" or a default object.
            throw throwable;
        }
        finally {
            //If we want to be sure that some of our code is executed even if we get an exception
            System.out.println("YourAspect's aroundAdvice's body is now executed After yourMethodAround is called.");
        }
        return returnObject;
    }

    @After("annotationPointCutDefinition() && atExecution()")
    // JointPoint = the reference of the call to the method
    public void printNewLine(JoinPoint pointcut){
        //Just prints new lines after each method that's executed in
        System.out.print("\n\r");
    }
}

Code Breakdown:

1. @Aspect Annotation

@Aspect
public class YourAspect {
    // Aspect code
}

2. @Pointcut Definitions

@Pointcut("@annotation(YourAnnotation)")
public void annotationPointCutDefinition() { }
@Pointcut("execution(* *(..))")
public void atExecution() { }

These pointcuts are used to select specific join points (places in the code where the advice will apply).

3. @Around Advice

@Around("@annotation(YourAnnotation) && execution(* *(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    // Advice body
}

4. @After Advice

@After("annotationPointCutDefinition() && atExecution()")
public void printNewLine(JoinPoint pointcut) {
    System.out.print("\n\r");
}