Skip to content

Spring AOP 通知详解:让你的代码更优雅地"插手"业务逻辑 🎯

什么是 Advice(通知)?

在 Spring AOP 的世界里,Advice(通知) 就像是一个"贴心的助手",它会在特定的时机自动执行一些额外的逻辑。想象一下,你有一个方法正在执行,而 Advice 就像是在方法执行的不同阶段插入的"钩子",可以在方法执行前、执行后或执行过程中做一些额外的工作。

NOTE

Advice 与 Pointcut(切点表达式)相关联,在匹配的方法执行前、后或周围运行。切点表达式可以是内联的,也可以引用命名的切点。

五种通知类型详解

1. Before Advice(前置通知)⏰

前置通知在目标方法执行之前运行,就像是方法的"门卫",可以进行权限检查、参数验证等操作。

kotlin
// 没有 AOP 的情况下,我们需要在每个方法中重复写验证逻辑
@Service
class UserService {
    fun createUser(user: User) {
        // 重复的验证逻辑
        if (!hasPermission()) { 
            throw SecurityException("无权限") 
        } 
        // 实际业务逻辑
        userRepository.save(user)
    }
    
    fun updateUser(user: User) {
        // 又是重复的验证逻辑
        if (!hasPermission()) { 
            throw SecurityException("无权限") 
        } 
        // 实际业务逻辑
        userRepository.update(user)
    }
}
kotlin
// 使用 AOP 后,验证逻辑统一管理
@Aspect
@Component
class SecurityAspect {
    
    @Before("execution(* com.example.service.*.*(..))")  
    fun checkPermission() {
        if (!hasPermission()) {
            throw SecurityException("无权限访问")
        }
        println("权限验证通过 ✅")
    }
}

@Service
class UserService {
    fun createUser(user: User) {
        // 不需要重复写验证逻辑,AOP 自动处理
        userRepository.save(user)
    }
    
    fun updateUser(user: User) {
        // 同样不需要重复验证逻辑
        userRepository.update(user)
    }
}

TIP

前置通知特别适合做安全检查参数验证日志记录等预处理工作。

2. After Returning Advice(后置返回通知)✅

后置返回通知在目标方法正常返回后执行,可以访问方法的返回值。

kotlin
@Aspect
@Component
class LoggingAspect {
    
    // 简单的后置通知
    @AfterReturning("execution(* com.example.service.*.*(..))")
    fun logSuccess() {
        println("方法执行成功 🎉")
    }
    
    // 获取返回值的后置通知
    @AfterReturning(
        pointcut = "execution(* com.example.service.UserService.findUser(..))",
        returning = "user"
    )
    fun logUserFound(user: User?) {  
        if (user != null) {
            println("找到用户: ${user.name}")
            // 可以进行缓存、统计等操作
            cacheService.put("user_${user.id}", user)
        }
    }
}

IMPORTANT

returning 属性的名称必须与通知方法的参数名称一致。返回值类型也会限制匹配范围,只有返回指定类型的方法才会被拦截。

3. After Throwing Advice(异常通知)❌

异常通知在目标方法抛出异常时执行,可以进行异常处理、日志记录等。

kotlin
@Aspect
@Component
class ExceptionHandlingAspect {
    
    // 捕获所有异常
    @AfterThrowing("execution(* com.example.service.*.*(..))")
    fun handleGeneralException() {
        println("方法执行出现异常 ⚠️")
        // 发送告警通知
        alertService.sendAlert("系统异常")
    }
    
    // 捕获特定类型的异常
    @AfterThrowing(
        pointcut = "execution(* com.example.service.*.*(..))",
        throwing = "ex"
    )
    fun handleSpecificException(ex: DataAccessException) {  
        println("数据库访问异常: ${ex.message}")
        // 记录详细的异常信息
        logService.error("数据库操作失败", ex)
        // 可以进行数据恢复操作
        recoveryService.attemptRecovery()
    }
}

WARNING

@AfterThrowing 不是通用的异常处理回调。它只接收来自连接点(目标方法)本身的异常,而不是来自其他 @After@AfterReturning 方法的异常。

4. After (Finally) Advice(最终通知)🔄

最终通知无论方法正常返回还是抛出异常都会执行,类似于 try-catch-finally 中的 finally 块。

kotlin
@Aspect
@Component
class ResourceManagementAspect {
    
    @After("execution(* com.example.service.*.*(..))")  
    fun releaseResources() {
        // 无论方法成功还是失败都会执行
        println("清理资源中... 🧹")
        
        // 释放数据库连接
        connectionPool.releaseConnection()
        
        // 清理临时文件
        tempFileService.cleanup()
        
        // 重置线程本地变量
        ThreadLocalContext.clear()
    }
}

使用场景

最终通知特别适合:

  • 资源清理(数据库连接、文件句柄等)
  • 统计信息记录
  • 重置状态
  • 发送完成通知

5. Around Advice(环绕通知)🔄

环绕通知是最强大的通知类型,它可以完全控制目标方法的执行,包括是否执行、何时执行、如何执行。

kotlin
@Aspect
@Component
class PerformanceAspect {
    
    @Around("execution(* com.example.service.*.*(..))")  
    fun measureExecutionTime(pjp: ProceedingJoinPoint): Any? {  
        val startTime = System.currentTimeMillis()
        
        return try {
            println("方法开始执行: ${pjp.signature.name}")
            
            // 执行目标方法
            val result = pjp.proceed()  
            
            val endTime = System.currentTimeMillis()
            println("方法执行完成,耗时: ${endTime - startTime}ms ⏱️")
            
            result
        } catch (ex: Exception) {
            val endTime = System.currentTimeMillis()
            println("方法执行异常,耗时: ${endTime - startTime}ms ❌")
            throw ex
        }
    }
}

环绕通知的高级用法:缓存

kotlin
@Aspect
@Component
class CachingAspect {
    
    private val cache = ConcurrentHashMap<String, Any>()
    
    @Around("@annotation(Cacheable)")  
    fun cacheResult(pjp: ProceedingJoinPoint): Any? {
        // 生成缓存键
        val cacheKey = generateCacheKey(pjp)
        
        // 先检查缓存
        cache[cacheKey]?.let { cachedResult ->
            println("缓存命中 🎯: $cacheKey")
            return cachedResult
        }
        
        // 缓存未命中,执行方法
        println("缓存未命中,执行方法: ${pjp.signature.name}")
        val result = pjp.proceed()
        
        // 将结果放入缓存
        result?.let { cache[cacheKey] = it }
        
        return result
    }
    
    private fun generateCacheKey(pjp: ProceedingJoinPoint): String {
        return "${pjp.signature.name}_${pjp.args.contentHashCode()}"
    }
}

// 使用注解标记需要缓存的方法
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Cacheable

WARNING

如果将环绕通知方法的返回类型声明为 void,则始终会向调用者返回 null。建议将返回类型声明为 Object,并通常返回 proceed() 的调用结果。

通知参数详解

访问 JoinPoint 信息

kotlin
@Aspect
@Component
class DetailedLoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    fun logMethodInfo(joinPoint: JoinPoint) {  
        println("=== 方法调用信息 ===")
        println("方法名: ${joinPoint.signature.name}")
        println("参数: ${joinPoint.args.contentToString()}")
        println("目标对象: ${joinPoint.target.javaClass.simpleName}")
        println("代理对象: ${joinPoint.`this`.javaClass.simpleName}")
    }
}

传递参数到通知

kotlin
@Aspect
@Component
class ParameterAspect {
    
    // 获取特定类型的参数
    @Before("execution(* com.example.service.*.*(..)) && args(user,..)")  
    fun validateUser(user: User) {  
        if (user.age < 0) {
            throw IllegalArgumentException("年龄不能为负数")
        }
        println("用户验证通过: ${user.name}")
    }
    
    // 使用命名切点
    @Pointcut("execution(* com.example.service.*.*(..)) && args(account,..)")
    private fun accountOperation(account: Account) {}
    
    @Before("accountOperation(account)")
    fun auditAccountOperation(account: Account) {
        println("账户操作审计: ${account.accountNumber}")
        auditService.log("账户操作", account.accountNumber)
    }
}

注解参数绑定

kotlin
// 自定义注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Auditable(val operation: String)

@Aspect
@Component
class AuditAspect {
    
    @Before("@annotation(auditable)")  
    fun auditOperation(auditable: Auditable) {  
        println("审计操作: ${auditable.operation}")
        auditService.record(auditable.operation, getCurrentUser())
    }
}

// 使用示例
@Service
class UserService {
    
    @Auditable(operation = "创建用户")  
    fun createUser(user: User): User {
        return userRepository.save(user)
    }
    
    @Auditable(operation = "删除用户")  
    fun deleteUser(userId: Long) {
        userRepository.deleteById(userId)
    }
}

通知执行顺序

当多个通知需要在同一个连接点执行时,Spring AOP 遵循以下规则:

kotlin
@Aspect
@Order(1)  
@Component
class SecurityAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    fun checkSecurity() {
        println("1. 安全检查")
    }
}

@Aspect
@Order(2)  
@Component
class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    fun logBefore() {
        println("2. 记录日志")
    }
    
    @AfterReturning("execution(* com.example.service.*.*(..))")
    fun logAfter() {
        println("4. 记录返回日志")
    }
}

@Service
class UserService {
    fun getUser(id: Long): User {
        println("3. 执行业务逻辑")
        return userRepository.findById(id)
    }
}

执行顺序:

  1. 安全检查(Order=1 的 @Before)
  2. 记录日志(Order=2 的 @Before)
  3. 执行业务逻辑
  4. 记录返回日志(Order=2 的 @AfterReturning)

NOTE

在同一个 @Aspect 类中,不同类型通知的优先级顺序:@Around > @Before > @After > @AfterReturning > @AfterThrowing

实际应用场景

场景1:API 接口监控

kotlin
@Aspect
@Component
class ApiMonitoringAspect {
    
    @Around("@annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping)")
    fun monitorApi(pjp: ProceedingJoinPoint): Any? {
        val startTime = System.currentTimeMillis()
        val apiName = pjp.signature.name
        
        try {
            val result = pjp.proceed()
            val duration = System.currentTimeMillis() - startTime
            
            // 记录成功的 API 调用
            metricsService.recordApiCall(apiName, duration, "SUCCESS")
            
            return result
        } catch (ex: Exception) {
            val duration = System.currentTimeMillis() - startTime
            
            // 记录失败的 API 调用
            metricsService.recordApiCall(apiName, duration, "ERROR")
            
            throw ex
        }
    }
}

场景2:数据库事务日志

完整的事务日志实现
kotlin
@Aspect
@Component
class TransactionLoggingAspect {
    
    @Before("@annotation(org.springframework.transaction.annotation.Transactional)")
    fun logTransactionStart(joinPoint: JoinPoint) {
        val methodName = joinPoint.signature.name
        val args = joinPoint.args
        
        println("🔄 事务开始: $methodName")
        println("📝 参数: ${args.contentToString()}")
    }
    
    @AfterReturning(
        pointcut = "@annotation(org.springframework.transaction.annotation.Transactional)",
        returning = "result"
    )
    fun logTransactionSuccess(result: Any?) {
        println("✅ 事务提交成功")
        if (result != null) {
            println("📤 返回结果: $result")
        }
    }
    
    @AfterThrowing(
        pointcut = "@annotation(org.springframework.transaction.annotation.Transactional)",
        throwing = "ex"
    )
    fun logTransactionFailure(ex: Exception) {
        println("❌ 事务回滚: ${ex.message}")
        // 发送告警
        alertService.sendTransactionFailureAlert(ex)
    }
}

总结

Spring AOP 的五种通知类型为我们提供了强大而灵活的横切关注点处理能力:

通知类型执行时机主要用途特点
@Before方法执行前权限检查、参数验证无法阻止方法执行
@AfterReturning正常返回后结果处理、缓存可访问返回值
@AfterThrowing抛出异常时异常处理、告警可访问异常对象
@After无论如何都执行资源清理类似 finally
@Around包围方法执行性能监控、缓存最强大,可控制执行

TIP

选择通知类型的原则:使用满足需求的最简单的通知类型。如果前置通知就能解决问题,就不要使用环绕通知。

通过合理使用这些通知类型,我们可以让代码更加简洁、可维护,同时实现强大的横切功能。记住,AOP 的核心思想是关注点分离,让业务逻辑专注于业务本身,而将横切关注点交给 AOP 来处理! 🚀