Five Practical Techniques for Spring AOP in Distributed Spring Boot Projects

In distributed system development, recurring cross-cutting concerns (such as logging, monitoring, security, etc.) often become a major source of code redundancy. This article shares five core techniques for using Spring AOP in a Spring Boot distributed architecture, based on real project experience, to help you improve code quality and development efficiency.

1. Distributed Log Tracking: Say Goodbye to Fragmented Logs

Pain Point Analysis

In a microservices architecture, a single request may span multiple service nodes, and traditional logging methods can lead to:

  1. Key business logs scattered across various services
  2. Inability to quickly associate the complete call chain of the same request
  3. Lack of unified request context information

AOP Solution

@Aspect
@Component
public class DistributedLogAspect {
    // Use MDC for cross-service log tracking
    private static final String TRACE_ID = "X-Trace-Id";

    @Pointcut("@annotation(com.example.annotation.GlobalLog)")
    public void logPointcut() {}

    @Around("logPointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        String traceId = UUID.randomUUID().toString();
        MDC.put(TRACE_ID, traceId);
        
        try {
            log.info("Start processing [{}]", joinPoint.getSignature());
            Object result = joinPoint.proceed();
            log.info("Completed [{}]", joinPoint.getSignature());
            return result;
        } finally {
            MDC.remove(TRACE_ID);
        }
    }
}

Usage Tips

  1. Use the <span>@GlobalLog</span> custom annotation to mark methods that need tracking
  2. Use MDC (Mapped Diagnostic Context) for cross-thread log association
  3. Integrate Sleuth for Zipkin distributed tracing (dependencies required)

2. Interface Performance Monitoring: Identify Slow Queries in Seconds

Scenario Pain Points

In production environments, the following issues often arise:

  • Difficulty in pinpointing fluctuations in interface response times
  • Lack of effective monitoring for slow database queries
  • Performance black holes in third-party service calls

Monitoring Aspect Implementation

@Aspect
@Component
public class PerformanceMonitorAspect {
    @Autowired
    private MetricsRecorder metricsRecorder;

    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}

    @Around("serviceLayer()")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long duration = System.currentTimeMillis() - start;
            String methodName = pjp.getSignature().getName();
            metricsRecorder.record(methodName, duration);
            
            if (duration > 1000) {
                log.warn("Slow method detected: {} took {}ms", methodName, duration);
            }
        }
    }
}

Practical Suggestions

  1. Integrate Prometheus + Grafana for visual monitoring
  2. Set different thresholds based on service types (e.g., HTTP services vs. DAO operations)
  3. Combine with Hystrix to implement a circuit breaker mechanism

3. Distributed Lock Aspect: Elegantly Solve Concurrency Issues

Typical Scenarios

  • Prevent duplicate submissions for order payments
  • Inventory deduction operations
  • Prevent multiple instances from executing scheduled tasks

Distributed Lock Aspect Implementation

@Aspect
@Component
public class DistributedLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(distributedLock)")
    public Object applyLock(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Throwable {
        String lockKey = generateLockKey(pjp, distributedLock);
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            if (lock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(), TimeUnit.SECONDS)) {
                return pjp.proceed();
            } else {
                throw new BusinessException("Operation too frequent, please try again later");
            }
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

Key Configuration

# Redisson Configuration
spring.redis.address=redis://127.0.0.1:6379
spring.redis.connection-pool-size=10

4. Service Circuit Breaker: Build a Resilient System

Circuit Breaker Aspect Implementation

@Aspect
@Component
public class CircuitBreakerAspect {
    private final CircuitBreakerRegistry registry = CircuitBreakerRegistry.ofDefaults();

    @Around("@annotation(circuitBreaker)")
    public Object protect(ProceedingJoinPoint pjp, CircuitBreaker circuitBreaker) throws Throwable {
        io.github.resilience4j.circuitbreaker.CircuitBreaker cb = registry.circuitBreaker("serviceA");
        
        return cb.executeSupplier(() -> {
            try {
                return pjp.proceed();
            } catch (Throwable t) {
                throw new RuntimeException(t);
            }
        });
    }
}

Circuit Breaker Strategy Configuration

resilience4j:
  circuitbreaker:
    configs:
      default:
        failureRateThreshold: 50
        minimumNumberOfCalls: 10
        slidingWindowType: TIME_BASED
        slidingWindowSize: 10s

5. Idempotency Control: Prevent Duplicate Requests

Idempotent Aspect Implementation

@Aspect
@Component
public class IdempotentAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("@annotation(idempotent)")
    public Object checkIdempotent(ProceedingJoinPoint pjp, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("X-Idempotent-Token");
        
        if (StringUtils.isEmpty(token)) {
            throw new BusinessException("Missing idempotent token");
        }
        
        Boolean result = redisTemplate.opsForValue().setIfAbsent(token, "1", 5, TimeUnit.MINUTES);
        if (Boolean.TRUE.equals(result)) {
            return pjp.proceed();
        } else {
            throw new BusinessException("Please do not submit repeatedly");
        }
    }
}

Pitfall Guide: AOP Usage Considerations

  1. Proxy Invalidity Issues:

  • Internal method calls will not trigger AOP
  • Use AopContext.currentProxy() to obtain the proxy object
  • Execution Order Control:

  • @Order(Ordered.HIGHEST_PRECEDENCE + 1)
    
  • Exception Handling Strategy:

    • Handle specific exceptions in @AfterThrowing
    • Avoid swallowing the original exception stack
  • Performance Optimization Suggestions:

    • Reduce IO operations within aspects
    • Use conditional pointcut expressions

    Conclusion

    By effectively utilizing Spring AOP, we can uniformly handle common concerns in distributed systems, achieving:

    ✅ Reduced code duplication (average reduction of 40% redundant code)✅ Improved system maintainability✅ Enhanced system stability✅ Rapid iteration of non-functional requirements

    Leave a Comment