SoFunction
Updated on 2025-05-04

Four AOP practical application scenarios and code implementations in SpringBoot

introduction

Oriented Programming (AOP) is one of the core functions of the Spring framework. It realizes unified maintenance of program functions through precompilation and run-time dynamic agents. In SpringBoot applications, AOP can help us elegantly solve cross-cutting concern issues, such as logging, permission control, performance monitoring, etc. These functions often run through the entire application but do not belong to the core logic of the business.

This article will introduce four practical AOP application scenarios in SpringBoot, including code implementation, core principles and practice.

Scenario 1: Logging and performance monitoring

Business Requirements

In enterprise-level applications, we usually need:

  • Record the calling status of API requests
  • Monitor the execution time of the method and discover performance bottlenecks
  • Tracking the incoming parameters and return results of method calls

Implementation plan

@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    /**
      * Define point cut: all methods under all controller packages
      */
    @Pointcut("execution(* .*.*(..))")
    public void controllerMethods() {}
    
    /**
      * Surround notification: log request log and execution time
      */
    @Around("controllerMethods()")
    public Object logAroundControllers(ProceedingJoinPoint joinPoint) throws Throwable {
        // Get the method signature        MethodSignature signature = (MethodSignature) ();
        String methodName = ();
        String className = ();
        
        // Record request parameters        String params = (());
        ("Request to {}.{} with params: {}", className, methodName, params);
        
        // Record the start time        long startTime = ();
        
        // Execute the target method        Object result;
        try {
            result = ();
            
            // Calculate execution time            long executionTime = () - startTime;
            
            // Record the return result and execution time            ("Response from {}.{} ({}ms): {}", 
                    className, methodName, executionTime, result);
            
            // Slow recording method            if (executionTime > 1000) {
                ("Slow execution detected! {}.{} took {}ms", 
                        className, methodName, executionTime);
            }
            
            return result;
        } catch (Exception e) {
            // Record exception information            ("Exception in {}.{}: {}", className, methodName, (), e);
            throw e;
        }
    }
    
    /**
      * Define service layer method cutting point
      */
    @Pointcut("execution(* .*.*(..))")
    public void serviceMethods() {}
    
    /**
      * Record key calls to the service layer method
      */
    @Before("serviceMethods() && @annotation(logMethod)")
    public void logServiceMethod(JoinPoint joinPoint, LogMethod logMethod) {
        MethodSignature signature = (MethodSignature) ();
        String methodName = ();
        
        // Get the parameter name        String[] paramNames = ();
        Object[] args = ();
        
        StringBuilder logMessage = new StringBuilder();
        ("Executing ").append(methodName).append(" with params: {");
        
        for (int i = 0; i < ; i++) {
            (paramNames[i]).append("=").append(args[i]);
            if (i <  - 1) {
                (", ");
            }
        }
        ("}");
        
        // Record logs according to the level set by the annotation        switch (()) {
            case DEBUG:
                (());
                break;
            case INFO:
                (());
                break;
            case WARN:
                (());
                break;
            case ERROR:
                (());
                break;
        }
    }
}

/**
  * Custom log annotations
  */
@Retention()
@Target()
public @interface LogMethod {
    LogLevel level() default ;
    
    public enum LogLevel {
        DEBUG, INFO, WARN, ERROR
    }
}

Example of usage

@Service
public class UserService {
    
    @LogMethod(level = )
    public User findById(Long id) {
        // Business logic        return (id).orElse(null);
    }
    
    @LogMethod(level = )
    public void updateUserStatus(Long userId, String status) {
        // Update user status    }
}

Extension: MDC implements request tracking

@Component
public class RequestIdFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            // Generate a unique ID for each request            String requestId = ().toString().replace("-", "");
            ("requestId", requestId);
            
            if (request instanceof HttpServletRequest) {
                HttpServletRequest httpRequest = (HttpServletRequest) request;
                // Record user information                Authentication auth = ().getAuthentication();
                if (auth != null && ()) {
                    ("userId", ());
                }
                ("remoteAddr", ());
            }
            
            (request, response);
        } finally {
            // Clean up the MDC after the request is completed            ();
        }
    }
}

Scenario 2: Permission control and security enhancement

Business Requirements

In enterprise applications, permission control is a common requirement:

  • Role-based interface access control
  • Fine-grained operation permission control
  • Recording of sensitive data access

Implementation plan

First, create a custom annotation:

@Retention()
@Target({, })
public @interface RequiresPermission {
    /**
      * The required permission code array can satisfy any of them.
      */
    String[] value() default {};
    
    /**
      * Permission logic type: AND (have all permissions at the same time), OR (just satisfy any permissions)
      */
    LogicalType logical() default ;
    
    public enum LogicalType {
        AND, OR
    }
}

Implement permissions section:

@Aspect
@Component
@Slf4j
public class PermissionAspect {
    
    @Autowired
    private UserService userService;
    
    /**
      * Definition point cut: All methods with @RequiresPermission annotation
      */
    @Pointcut("@annotation()")
    public void permissionCheck() {}
    
    /**
      * Permission verification pre-notification
      */
    @Before("permissionCheck() && @annotation(requiresPermission)")
    public void checkPermission(JoinPoint joinPoint, RequiresPermission requiresPermission) {
        // Get the current user        User currentUser = getCurrentUser();
        if (currentUser == null) {
            throw new UnauthorizedException("User not logged in or session expired");
        }
        
        // Get the user permission list        Set<String> userPermissions = (());
        
        // Get permissions required in the annotation        String[] requiredPermissions = ();
         logicalType = ();
        
        // Permission verification        boolean hasPermission = false;
        
        if (logicalType == ) {
            // Just satisfy any permissions            for (String permission : requiredPermissions) {
                if ((permission)) {
                    hasPermission = true;
                    break;
                }
            }
        } else {
            // All permissions must be met at the same time            hasPermission = true;
            for (String permission : requiredPermissions) {
                if (!(permission)) {
                    hasPermission = false;
                    break;
                }
            }
        }
        
        if (!hasPermission) {
            ("user {} Try to access unauthorized resources: {}.{}", 
                    (),
                    ().getDeclaringTypeName(),
                    ().getName());
            
            throw new ForbiddenException("Insufficient permissions, unable to perform this operation");
        }
        
        // Record sensitive operations        ("user {} Perform an authorized operation: {}.{}", 
                (), 
                ().getDeclaringTypeName(),
                ().getName());
    }
    
    /**
      * Definition point cut: Method with @RequiresRole annotation
      */
    @Pointcut("@annotation()")
    public void roleCheck() {}
    
    /**
      * Role check pre-notification
      */
    @Before("roleCheck() && @annotation(requiresRole)")
    public void checkRole(JoinPoint joinPoint, RequiresRole requiresRole) {
        // Get the current user        User currentUser = getCurrentUser();
        if (currentUser == null) {
            throw new UnauthorizedException("User not logged in or session expired");
        }
        
        // Get user role        Set<String> userRoles = (());
        
        // Get the role required in the annotation        String[] requiredRoles = ();
        
        // Role verification        boolean hasRole = false;
        for (String role : requiredRoles) {
            if ((role)) {
                hasRole = true;
                break;
            }
        }
        
        if (!hasRole) {
            ("user {} Try to access unauthorized role resources: {}.{}", 
                    (),
                    ().getDeclaringTypeName(),
                    ().getName());
            
            throw new ForbiddenException("Insufficient roles, unable to perform this operation");
        }
    }
    
    /**
      * Data permission filtering point: for query method
      */
    @Pointcut("execution(* .*.find*(..))")
    public void dataPermissionFilter() {}
    
    /**
      * Data permission filtering notification
      */
    @Around("dataPermissionFilter()")
    public Object filterDataByPermission(ProceedingJoinPoint joinPoint) throws Throwable {
        // Get the current user        User currentUser = getCurrentUser();
        
        // The original method is executed by default        Object result = ();
        
        // If you are an administrator, there is no need to filter data        if ((())) {
            return result;
        }
        
        // Filter the query results        if (result instanceof Collection) {
            Collection<?> collection = (Collection<?>) result;
            // Implement data filtering logic...        } else if (result instanceof Page) {
            Page<?> page = (Page<?>) result;
            // Implement paging data filtering...        }
        
        return result;
    }
    
    /**
      * Get the current logged in user
      */
    private User getCurrentUser() {
        Authentication authentication = ().getAuthentication();
        if (authentication == null || !()) {
            return null;
        }
        
        Object principal = ();
        if (principal instanceof User) {
            return (User) principal;
        }
        
        return null;
    }
}

/**
  * Custom role notes
  */
@Retention()
@Target({, })
public @interface RequiresRole {
    String[] value();
}

Example of usage

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    @RequiresPermission("user:list")
    public List<User> listUsers() {
        return ();
    }
    
    @GetMapping("/{id}")
    @RequiresPermission("user:view")
    public User getUser(@PathVariable Long id) {
        return (id);
    }
    
    @PostMapping
    @RequiresPermission(value = {"user:create", "user:edit"}, logical = )
    public User createUser(@RequestBody User user) {
        return (user);
    }
    
    @DeleteMapping("/{id}")
    @RequiresRole("ADMIN")
    public void deleteUser(@PathVariable Long id) {
        (id);
    }
    
    @PutMapping("/{id}/status")
    @RequiresPermission(value = {"user:edit", "user:manage"}, logical = )
    public User updateUserStatus(@PathVariable Long id, @RequestParam String status) {
        return (id, status);
    }
}

Scenario 3: Custom cache implementation

Business Requirements

Caching is a key means to improve application performance, which can be achieved through AOP:

  • Custom cache policies to meet specific business needs
  • Fine-grained cache control
  • Flexible cache key generation and expiration strategies

Implementation plan

First define the cache annotation:

@Retention()
@Target()
public @interface Cacheable {
    /**
      * Cache name
      */
    String cacheName();
    
    /**
      * Cache key expression, supports SpEL expression
      */
    String key() default "";
    
    /**
      * Expiration time (seconds)
      */
    long expireTime() default 300;
    
    /**
      * Whether to use method parameters as part of the cache key
      */
    boolean useMethodParameters() default true;
}

@Retention()
@Target()
public @interface CacheEvict {
    /**
      * Cache name
      */
    String cacheName();
    
    /**
      * cache key expression
      */
    String key() default "";
    
    /**
      * Whether to clear all caches
      */
    boolean allEntries() default false;
}

Implement cache sections:

@Aspect
@Component
@Slf4j
public class CacheAspect {
    
    @Autowired
    private RedisTemplate&lt;String, Object&gt; redisTemplate;
    
    @Autowired
    private CacheKeyGenerator keyGenerator;
    
    /**
      * Define cache fetch point cut
      */
    @Pointcut("@annotation()")
    public void cacheableOperation() {}
    
    /**
      * Define cache clear cut points
      */
    @Pointcut("@annotation()")
    public void cacheEvictOperation() {}
    
    /**
      * Cache surround notifications
      */
    @Around("cacheableOperation() &amp;&amp; @annotation(cacheable)")
    public Object handleCacheable(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        // Generate cache key        String cacheKey = generateCacheKey(joinPoint, (), (), ());
        
        // Check whether there is data in the cache        Boolean hasKey = (cacheKey);
        if ((hasKey)) {
            Object cachedValue = ().get(cacheKey);
            ("Cache hit for key: {}", cacheKey);
            return cachedValue;
        }
        
        // Cache misses, execute the method to get the result        ("Cache miss for key: {}", cacheKey);
        Object result = ();
        
        // Save the result in cache        if (result != null) {
            ().set(cacheKey, result, (), );
            ("Stored in cache with key: {}, expire time: {}s", cacheKey, ());
        }
        
        return result;
    }
    
    /**
      * Cache clear pre-notification
      */
    @Before("cacheEvictOperation() &amp;&amp; @annotation(cacheEvict)")
    public void handleCacheEvict(JoinPoint joinPoint, CacheEvict cacheEvict) {
        if (()) {
            // Clear all entries under this cache name            String cachePattern = () + ":*";
            Set&lt;String&gt; keys = (cachePattern);
            if (keys != null &amp;&amp; !()) {
                (keys);
                ("Cleared all cache entries with pattern: {}", cachePattern);
            }
        } else {
            // Clear the cache of the specified key            String cacheKey = generateCacheKey(joinPoint, (), (), true);
            (cacheKey);
            ("Cleared cache with key: {}", cacheKey);
        }
    }
    
    /**
      * Generate cache key
      */
    private String generateCacheKey(JoinPoint joinPoint, String cacheName, String keyExpression, boolean useParams) {
        StringBuilder keyBuilder = new StringBuilder(cacheName).append(":");
        
        // If a custom key expression is provided        if ((keyExpression)) {
            String evaluatedKey = (keyExpression, joinPoint);
            (evaluatedKey);
        } else if (useParams) {
            //Use method signature and parameters as keys            MethodSignature signature = (MethodSignature) ();
            String methodName = ();
            (methodName);
            
            // Add parameters            Object[] args = ();
            if (args != null &amp;&amp;  &gt; 0) {
                for (Object arg : args) {
                    if (arg != null) {
                        (":").append(());
                    } else {
                        (":null");
                    }
                }
            }
        } else {
            // Use only method names            (().getName());
        }
        
        return ();
    }
}

/**
  * Cache key generator, supports SpEL expressions
  */
@Component
public class CacheKeyGenerator {
    
    private final ExpressionParser parser = new SpelExpressionParser();
    private final StandardEvaluationContext context = new StandardEvaluationContext();
    
    public String generateKey(String expression, JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) ();
        Method method = ();
        Object[] args = ();
        String[] parameterNames = ();
        
        // Set method parameters to context variables        for (int i = 0; i &lt; ; i++) {
            (parameterNames[i], args[i]);
        }
        
        // Add extra metadata        ("method", ());
        ("class", ().getSimpleName());
        ("target", ());
        
        // Execute expression        Expression exp = (expression);
        return (context, );
    }
}

Redis configuration

@Configuration
public class RedisConfig {
    
    @Bean
    public RedisTemplate&lt;String, Object&gt; redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate&lt;String, Object&gt; template = new RedisTemplate&lt;&gt;();
        (connectionFactory);
        
        // Serialize values ​​using Jackson2JsonRedisSerializer        Jackson2JsonRedisSerializer&lt;Object&gt; jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer&lt;&gt;();
        ObjectMapper om = new ObjectMapper();
        (, );
        (, .NON_FINAL);
        (om);
        
        // Set the serialization method of keys to strings        (new StringRedisSerializer());
        // Values ​​are serialized using JSON        (jackson2JsonRedisSerializer);
        
        // Hash key also uses strings        (new StringRedisSerializer());
        // Hash values ​​are serialized using JSON        (jackson2JsonRedisSerializer);
        
        ();
        return template;
    }
}

Example of usage

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    @Cacheable(cacheName = "products", expireTime = 3600)
    public Product getById(Long id) {
        return (id).orElse(null);
    }
    
    @Cacheable(cacheName = "products", key = "'list:category:' + #categoryId", expireTime = 1800)
    public List<Product> getByCategory(Long categoryId) {
        return (categoryId);
    }
    
    @CacheEvict(cacheName = "products", allEntries = true)
    public Product save(Product product) {
        return (product);
    }
    
    @CacheEvict(cacheName = "products", key = "'list:category:' + #")
    public void deleteProductFromCategory(Product product) {
        (product);
    }
}

Scenario 4: Unified exception handling and retry mechanism

Business Requirements

In distributed systems or complex business scenarios, we often need:

  • Handle exceptions gracefully
  • Automatically retry certain operations
  • Idepotency assurance of key operations

Implementation plan

First define retry and exception handling annotations:

@Retention()
@Target()
public @interface Retryable {
    /**
      * Maximum number of retry
      */
    int maxAttempts() default 3;
    
    /**
      * Retry interval (milliseconds)
      */
    long backoff() default 1000;
    
    /**
      * Specify the captured exception type
      */
    Class&lt;? extends Throwable&gt;[] value() default {};
    
    /**
      * Retry the policy
      */
    RetryStrategy strategy() default ;
    
    /**
      * Retry the policy enumeration
      */
    enum RetryStrategy {
        /**
          * Fixed interval
          */
        FIXED,
        
        /**
          * Exponential backoff
          */
        EXPONENTIAL
    }
}

@Retention()
@Target()
public @interface Idempotent {
    /**
      * Idepotential key expression
      */
    String key();
    
    /**
      * Expiration time (seconds)
      */
    long expireSeconds() default 300;
}

Implement exception handling and retrying the section:

@Aspect
@Component
@Slf4j
public class RetryAspect {
    
    @Autowired
    private RedisTemplate&lt;String, String&gt; redisTemplate;
    
    /**
      * Define the retry operation point cut
      */
    @Pointcut("@annotation()")
    public void retryableOperation() {}
    
    /**
      * Define idempotent operation cut point
      */
    @Pointcut("@annotation()")
    public void idempotentOperation() {}
    
    /**
      * Retry the surround notification
      */
    @Around("retryableOperation() &amp;&amp; @annotation(retryable)")
    public Object handleRetry(ProceedingJoinPoint joinPoint, Retryable retryable) throws Throwable {
        int attempts = 0;
        Class&lt;? extends Throwable&gt;[] retryableExceptions = ();
         strategy = ();
        
        while (true) {
            attempts++;
            try {
                // Execute the target method                return ();
            } catch (Throwable t) {
                // Check whether it is an exception type that needs to be retryed                boolean shouldRetry = false;
                for (Class&lt;? extends Throwable&gt; exceptionType : retryableExceptions) {
                    if ((t)) {
                        shouldRetry = true;
                        break;
                    }
                }
                
                // If no retry is required, or the maximum number of retryes is reached, an exception is thrown                if (!shouldRetry || attempts &gt;= ()) {
                    ("Method {} failed after {} attempts: {}", 
                            ().getName(), attempts, ());
                    throw t;
                }
                
                // Calculate the retry waiting time                long waitTime;
                if (strategy == ) {
                    // Exponential backoff: Basic time * 2^(Number of attempts-1)                    waitTime = () * (long) (2, attempts - 1);
                } else {
                    // Fixed interval                    waitTime = ();
                }
                
                ("Retrying {} (attempt {}/{}) after {} ms due to: {}", 
                        ().getName(), 
                        attempts, 
                        (), 
                        waitTime, 
                        ());
                
                // Try again after waiting for the specified time                (waitTime);
            }
        }
    }
    
    /**
      * Idepotency Surround Notification
      */
    @Around("idempotentOperation() &amp;&amp; @annotation(idempotent)")
    public Object handleIdempotent(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // parse idempotent keys        String idempotentKey = resolveIdempotentKey(joinPoint, ());
        String lockKey = "idempotent:" + idempotentKey;
        
        // Try setting up a distributed lock        Boolean success = ().setIfAbsent(
                lockKey, "PROCESSING", (), );
        
        if ((success)) {
            try {
                // Acquisition lock successfully, execute business logic                Object result = ();
                
                // Save the result to Redis                String resultKey = "result:" + lockKey;
                ().set(
                        resultKey, new ObjectMapper().writeValueAsString(result), 
                        (), );
                
                // Mark as processed                ().set(
                        lockKey, "COMPLETED", (), );
                
                return result;
            } catch (Throwable t) {
                // Processing failed, marking error                ().set(
                        lockKey, "ERROR:" + (), (), );
                throw t;
            }
        } else {
            // Acquisition of lock failed, indicating that the operation is being processed or has been processed            String status = ().get(lockKey);
            
            if ("PROCESSING".equals(status)) {
                // Still under processing                throw new ConcurrentOperationException("The operation is being processed, please do not repeat submission");
            } else if (status != null &amp;&amp; ("ERROR:")) {
                // An error occurred before processing                throw new OperationFailedException("Operation processing failed: " + (6));
            } else if ("COMPLETED".equals(status)) {
                // Completed, try to return the previous result                String resultKey = "result:" + lockKey;
                String resultJson = ().get(resultKey);
                
                if (resultJson != null) {
                    // Deserialize JSON into a response object                    Method method = ((MethodSignature) ()).getMethod();
                    Class&lt;?&gt; returnType = ();
                    
                    try {
                        return new ObjectMapper().readValue(resultJson, returnType);
                    } catch (Exception e) {
                        ("Failed to deserialize cached result: {}", ());
                    }
                }
                
                // If the result is not found or the deserialization fails, the message that was successful but the last result cannot be provided                throw new OperationAlreadyCompletedException("The operation has been processed successfully, but the result of the last operation cannot be provided");
            }
            
            // The status is unknown, throw an exception            throw new OperationFailedException("Operation status unknown");
        }
    }
    
    /**
      * parse idempotent key expressions
      */
    private String resolveIdempotentKey(JoinPoint joinPoint, String keyExpression) {
        MethodSignature signature = (MethodSignature) ();
        String[] paramNames = ();
        Object[] args = ();
        
        // Create expression context        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // Add method parameters        for (int i = 0; i &lt; ; i++) {
            (paramNames[i], args[i]);
        }
        
        // Add class and method names        ("method", ().getName());
        ("class", ().getSimpleName());
        
        // parse expression        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = (keyExpression);
        
        return (context, );
    }
}

// Custom exception classpublic class ConcurrentOperationException extends RuntimeException {
    public ConcurrentOperationException(String message) {
        super(message);
    }
}

public class OperationFailedException extends RuntimeException {
    public OperationFailedException(String message) {
        super(message);
    }
}

public class OperationAlreadyCompletedException extends RuntimeException {
    public OperationAlreadyCompletedException(String message) {
        super(message);
    }
}

Example of usage

@Service
public class PaymentService {
    
    @Autowired
    private PaymentGateway paymentGateway;
    
    @Autowired
    private OrderRepository orderRepository;
    
    /**
      * Remote payment processing, you may encounter network problems and need to try again
      */
    @Retryable(
        value = {, , },
        maxAttempts = 3,
        backoff = 2000,
        strategy = 
    )
    public PaymentResult processPayment(String orderId, BigDecimal amount) {
        ("Processing payment for order {} with amount {}", orderId, amount);
        
        // Call remote payment gateway        return (orderId, amount);
    }
    
    /**
      * Order refunds are required to ensure idempotence
      */
    @Idempotent(key = "'refund:' + #orderId", expireSeconds = 3600)
    public RefundResult refundOrder(String orderId) {
        Order order = (orderId)
                .orElseThrow(() -&gt; new OrderNotFoundException("Order not found: " + orderId));
        
        // Verify order status        if (!"PAID".equals(())) {
            throw new InvalidOrderStatusException("Cannot refund order with status: " + ());
        }
        
        // Call the payment gateway to refund        RefundResult result = ((), ());
        
        // Update order status        ("REFUNDED");
        (());
        (order);
        
        return result;
    }
}

@Service
public class StockService {
    
    @Autowired
    private StockRepository stockRepository;
    
    /**
      * Deduct inventory, retry and idempotence are required in a distributed environment
      */
    @Retryable(
        value = {, },
        maxAttempts = 5,
        backoff = 500
    )
    @Idempotent(key = "'deduct:' + #orderId")
    public void deductStock(String orderId, List&lt;OrderItem&gt; items) {
        // Check whether there is an inventory record        for (OrderItem item : items) {
            Stock stock = (());
            
            if (stock == null) {
                throw new ProductNotFoundException("Product not found: " + ());
            }
            
            if (() &lt; ()) {
                throw new StockInsufficientException(
                        "Insufficient stock for product: " + () +
                        ", requested: " + () +
                        ", available: " + ());
            }
        }
        
        // Implement inventory deductions        for (OrderItem item : items) {
            ((), ());
        }
    }
}

in conclusion

AOP is a powerful programming paradigm in SpringBoot. Through these modes, we can decouple cross-cutting concerns from business logic, making the code more modular and maintainable, while improving the robustness and security of the system.

The above is the detailed content of the four AOP practical application scenarios and code implementations in SpringBoot. For more information about SpringBoot AOP application scenarios, please pay attention to my other related articles!