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
@Transactionalprocessing
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
@AroundAdvice: Wraps around method execution- Pointcut Expressions: Targets methods with specific annotations
- 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:
- DataSource routing happens FIRST
- Then transaction management starts
- 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
Post a Comment