Aspect Oriented Programming

Aspec Oriented Programming

In enterprise applications, certain tasks like logging, transaction management, and data validation are needed across various objects and modules. These tasks, known as crosscutting concerns, affect many parts of the application. In Object-Oriented Programming (OOP), we organize code using classes. In Aspect-Oriented Programming (AOP), we use aspects to handle these crosscutting tasks, and they apply to multiple classes. Spring AOP helps remove the need for classes to directly handle crosscutting tasks, something that regular OOP can’t do. Spring and Spring Boot offer strong AOP support, enabling you to manage concerns like logging, security, and transactions separately from your main application logic.

What is AOP?

  1. AOP allows you to dynamically integrate cross-cutting concerns like logging, security, or transactions into your code before, after, or around your core logic using simple, pluggable configurations. This means you can add or change functionality without touching the main business logic.
  2. AOP helps separate non-business logic concerns such as logging, auditing, security, and transaction management from the core code. This keeps your business logic clean and easier to maintain by centralizing these repetitive tasks into reusable components.
  3. AOP is a programming approach designed to improve modularity by separating cross-cutting concerns. It works by adding extra behaviors (e.g., logging or security checks) to your code without actually modifying the core code itself, making the system more modular and maintainable.


Key Terminology in AOP (AOP Jargons)

When defining an aspect or setting up configurations, we need to follow the "WWW" approach.


  1. WHAT code or logic we want the spring to execute when you call a specific metho. This is called as Aspect.
  2. WHEN the spring need to execute the given Aspect. For example is it before or after method call. This is called as Advice.
  3. WHICH method inside app that framework needs to intercept and execute the given Aspect. This is called as a Pointcut.
  4. Join Point is a specific point in the execution of a program, such as a method call, constructor invocation, or field access.

Advice

This is the action taken by an aspect at a particular join point. There are five types of advice:


Spring


Writing Expressions in AOP

In Spring AOP, expressions are used to define pointcuts, which specify where advice should be applied. These expressions are typically written using AspectJ expression language.

1. Basic Structure of Pointcut Expressions

2. Common Expression Patterns
  1. Match All Methods in a Package
    @Pointcut("execution(* com.example.service.*.*(..))")

    This expression matches all methods in any class within the com.example.service package.

  2. Match Specific Method
    @Pointcut("execution(void com.example.service.MyService.performTask())")

    This matches the performTask method in the MyService class.

  3. Match All Methods with Specific Return Type
    @Pointcut("execution(String com.example.service.*.get*(..))")

    This matches all methods in any class in the package that start with "get" and return a String.

  4. Match Methods with Specific Parameters
    @Pointcut("execution(* com.example.service.*.addUser(String, ..))")

    This matches the addUser method that takes a String as the first parameter, regardless of the second parameter type.

  5. Match Methods with Annotations
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")

    This matches any method annotated with @GetMapping.

  6. Combining Pointcuts
    @Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.repository.*.*(..))")

    This expression matches all methods in both the service and repository packages.


3. Using Annotatio Example
Spring

Example: Using Pointcut Expressions in Advice


                    import org.aspectj.lang.annotation.Aspect;
                    import org.aspectj.lang.annotation.Before;
                    import org.springframework.stereotype.Component;
                    
                    @Aspect
                    @Component
                    public class LoggingAspect {
                        
                        // Pointcut expression to match all methods in the service package
                        @Pointcut("execution(* com.example.service.*.*(..))")
                        public void serviceMethods() {}
                    
                        // Advice that runs before the matched methods
                        @Before("serviceMethods()")
                        public void logBefore() {
                            System.out.println("Log: A service method is about to be executed.");
                        }
                    }
                    

AOP Proxies in Spring

What is Proxy?
A proxy is an object that represents another object. In Spring AOP, proxies are used to apply aspects to target objects without modifying the actual code of those objects. Proxies act as intermediaries that handle method calls to the target objects and enable the application of cross-cutting concerns like logging, security, or transactions..

Types of Proxies
Spring AOP can create two types of proxies:
Spring
When to Use Which Proxy

Combining It All with Example

1. Logging

    @Aspect
    @Component
    public class LoggingAspect {
    
        @Before("execution(* com.example.service.*.*(..))")
        public void logBeforeMethodExecution(JoinPoint joinPoint) {
            System.out.println("Entering method: " + joinPoint.getSignature().getName());
        }
    
        @AfterReturning("execution(* com.example.service.*.*(..))")
        public void logAfterMethodExecution(JoinPoint joinPoint) {
            System.out.println("Exiting method: " + joinPoint.getSignature().getName());
        }
    
        @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
        public void logAfterException(JoinPoint joinPoint, Throwable error) {
            System.out.println("Exception in method: " + joinPoint.getSignature().getName() + " with error: " + error.getMessage());
        }
    }
                        
2. Transaction Management

    @Aspect
    @Component
    public class TransactionAspect {
    
        @Autowired
        private PlatformTransactionManager transactionManager;
    
        @Around("execution(* com.example.service.*.*(..))")
        public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
            TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
            Object result;
            try {
                result = joinPoint.proceed();
                transactionManager.commit(status);
            } catch (Throwable ex) {
                transactionManager.rollback(status);
                throw ex;
            }
            return result;
        }
    }
                        
3. Security

    @Aspect
    @Component
    public class SecurityAspect {
    
        @Before("execution(* com.example.service.*.*(..)) && @annotation(Secured)")
        public void checkSecurity(JoinPoint joinPoint) {
            // Security logic here
            if (!isUserAuthorized()) {
                throw new SecurityException("User is not authorized to perform this operation.");
            }
        }
    
        private boolean isUserAuthorized() {
            // Implement your security check logic
            return true; // Dummy implementation
        }
    }
                        
4. Performance Monitoring

    @Aspect
    @Component
    public class PerformanceMonitoringAspect {

        @Around("execution(* com.example.service.*.*(..))")
        public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
            long startTime = System.currentTimeMillis();
            Object result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            System.out.println("Execution time of " + joinPoint.getSignature().getName() + " : " + (endTime - startTime) + " ms");
            return result;
        }
    }
                        
5. Exception handling

    @Aspect
    @Component
    public class ExceptionHandlingAspect {
    
        @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
        public void handleException(JoinPoint joinPoint, Throwable ex) {
            // Centralized exception handling logic
            System.out.println("Exception in method: " + joinPoint.getSignature().getName() + " with message: " + ex.getMessage());
        }
    }
                        
6. Audit Logging

    @Aspect
    @Component
    public class AuditLoggingAspect {
    
        @After("execution(* com.example.service.*.*(..)) && @annotation(Auditable)")
        public void logAudit(JoinPoint joinPoint) {
            // Audit logging logic
            System.out.println("Audit log for method: " + joinPoint.getSignature().getName());
        }
    }