Problem: The problem is the tight coupling of different concerns (aspects) with the main business application code. Non-business features that are expected to be implemented or common business features that are scattered across multiple places in the application are called cross-cutting concerns (aspects). Tight coupling of code is hard to manage, especially in a distributed application environment.
For example, suppose logging the input and output of every API and method across multiple places in the application becomes cumbersome to add or remove small changes. You have to put extra unnecessary effort into managing a time-consuming approach. It is also by-passing the open-closed principle.
Logging, Security, Transactions etc are mandatory concerns (aspects) which are expected to be implemented in a distributed application environment.
Solution: Spring AOP (Aspect Oriented Programming) is an idea (programming paradigm) that separates the concerns which are not related to application business, for example, logging, security, transactions etc but It is mandatory to implement. Spring AOP is like a pluggable way of including or excluding the non-business feature code with the main application code using Advices, Pointcut expressions, etc.
Key components of Spring AOP:
1. Aspect: An aspect is a spring class where we implement the logic in the methods expected to be applied at different points of time in the execution process of application.
@Aspect
@Component
public class LoggingAspect {}
2. Advice: Advice defines when logic (concern/aspect) is going to be executed on matching pointcut expression. Spring AOP provides five types of advice:
1. Before: Runs before method execution
2. After: Runs after method execution
3. AfterReturning: Runs after successful execution
4. AfterThrowing: Runs only if an exception occurs
5. Around: Can modify the execution flow
@Aspect
@Component
public class LoggingAspect {
// Before Advice: Runs before method execution
@Before("execution(* in.coderstuff.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("Before: " + joinPoint.getSignature().getName());
}
// After Advice: Runs after method execution (success or failure)
@After("execution(* in.coderstuff.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
log.info("After: " + joinPoint.getSignature().getName());
}
// AfterReturning Advice: Runs after successful execution
@AfterReturning("execution(* in.coderstuff.service.*.*(..))")
public void logAfterReturning(JoinPoint joinPoint) {
log.info("After Returning: " + joinPoint.getSignature().getName());
}
// AfterThrowing Advice: Runs only if an exception occurs
@AfterThrowing("execution(* in.coderstuff.service.*.*(..))")
public void logAfterThrowing(JoinPoint joinPoint) {
log.info("AfterThrowing:Exception in" + joinPoint.getSignature().getName());
}
// Around Advice: Can modify the execution flow
@Around("execution(* in.coderstuff.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around (Before): "+joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
log.info("Around (After): "+ joinPoint.getSignature().getName());
return result;
}
}
3. JoinPoint: A joinpoint is a place in the application code going to be targeted based on pointcut expression. Example: Pointcut Expression (This matches all methods inside the in.codestuff.service package.)
@Before("execution(* in.coderstuff.service.*.*(..))")
public void serviceLayer() {}
This applies the Before Advice to all service methods.
4. Pointcut: Pointcut expression defines “where” the logic is executed based on advice. Advice decides “when” using different annotations before, after, after returning, after throwing and around. Pointcut decides place as per expression.