Aspect-Oriented Programming (AOP) in Spring Boot

Aspect-Oriented Programming (AOP) in Spring Boot

What is AOP?

Aspect-Oriented Programming (AOP) is a programming paradigm that allows you to separate cross-cutting concerns from your business logic. Cross-cutting concerns are functionalities that affect multiple parts of an application, such as:

  • Logging
  • Security
  • Transaction management
  • Database routing
  • Performance monitoring

Understanding @Aspect and @Order(0)

@Aspect

Marks a class as an aspect - a modular unit of cross-cutting concern. It contains advice (code to execute) and pointcuts (where to execute).

@Order(0)

Defines the execution priority when multiple aspects target the same method:

  • Lower numbers = Higher priority
  • @Order(0) executes before @Order(1), @Order(2), etc.
  • In this case, routing happens before Spring's @Transactional processing

DataSourceRoutingAspect Explained

@Aspect
@Component
@Order(0) // Execute BEFORE @Transactional aspect
@Log4j2
public class DataSourceRoutingAspect {
    // This aspect intercepts methods to route database calls
    // to either reader or writer datasources
}

How It Works

  1. @Around Advice: Wraps around method execution
  2. Pointcut Expressions: Targets methods with specific annotations
  3. Context Switching: Sets datasource before method execution, clears after

The Three Routing Strategies

// 1. Explicit reader routing
@Around("@annotation(readOnlyDataSource)")
public Object routeReadOnlyDataSource(...) {
    DataSourceContextHolder.setDataSourceType("reader");
    // Execute method
    // Clear context
}

// 2. Explicit writer routing
@Around("@annotation(writeOnlyDataSource)")
public Object routeWriteOnlyDataSource(...) {
    DataSourceContextHolder.setDataSourceType("writer");
}

// 3. Auto-routing based on @Transactional(readOnly)
@Around("@annotation(transactional)")
public Object routeTransactional(...) {
    if (transactional.readOnly()) {
        // Route to reader
    } else {
        // Route to writer
    }
}

Why @Order(0) is Critical

Spring's @Transactional aspect has a default order. By using @Order(0), we ensure:

  1. DataSource routing happens FIRST
  2. Then transaction management starts
  3. Prevents routing conflicts
Flow: @Order(0) DataSourceRouting → @Transactional → Your Method → Commit/Rollback → Clear Context

Key AOP Concepts

  • Aspect: Module containing cross-cutting logic
  • Advice: Action taken (e.g., @Around, @Before, @After)
  • Pointcut: Where advice is applied (e.g., @annotation(...))
  • Join Point: Point in execution where advice can be applied
  • Weaving: Process of applying aspects to target objects

Complete Implementation

package com.sysco.sales_order_enterprise_service.config;

import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Aspect
@Component
@Order(0) // Execute before @Transactional
@Log4j2
public class DataSourceRoutingAspect {

    @Around("@annotation(readOnlyDataSource)")
    public Object routeReadOnlyDataSource(ProceedingJoinPoint joinPoint, ReadOnlyDataSource readOnlyDataSource) throws Throwable {
        try {
            DataSourceContextHolder.setDataSourceType("reader");
            log.debug("Routing to reader datasource for method: {}", joinPoint.getSignature().getName());
            return joinPoint.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }

    @Around("@annotation(writeOnlyDataSource)")
    public Object routeWriteOnlyDataSource(ProceedingJoinPoint joinPoint, WriteOnlyDataSource writeOnlyDataSource) throws Throwable {
        try {
            DataSourceContextHolder.setDataSourceType("writer");
            log.debug("Routing to writer datasource for method: {}", joinPoint.getSignature().getName());
            return joinPoint.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }

    @Around("@annotation(transactional)")
    public Object routeTransactional(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable {
        try {
            if (transactional.readOnly()) {
                DataSourceContextHolder.setDataSourceType("reader");
                log.debug("Routing to reader datasource (read-only transaction) for method: {}", joinPoint.getSignature().getName());
            } else {
                DataSourceContextHolder.setDataSourceType("writer");
                log.debug("Routing to writer datasource (write transaction) for method: {}", joinPoint.getSignature().getName());
            }
            return joinPoint.proceed();
        } finally {
            DataSourceContextHolder.clearDataSourceType();
        }
    }
}

This pattern enables clean separation between business logic and infrastructure concerns like database routing.

Comments

Popular posts from this blog

Design Patterns

AWS Networking & IAM

Hibernate (Java) -- by jps sasadara