Skip to content

Spring AOP Advice API 深度解析 🎯

概述

Spring AOP 的 Advice API 是面向切面编程的核心组件之一。如果说 Pointcut 定义了"在哪里"切入,那么 Advice 就定义了"做什么"。它是横切关注点的具体实现,让我们能够在不修改业务代码的情况下,优雅地处理日志记录、事务管理、安全检查等通用功能。

NOTE

Advice 在 Spring 中是一个 Spring Bean,这意味着它享有完整的 Spring 容器管理能力,包括依赖注入、生命周期管理等特性。

Advice 生命周期管理 🔄

共享策略:Per-Class vs Per-Instance

Spring AOP 提供了两种 Advice 实例管理策略:

kotlin
@Component
class TransactionAdvice : MethodInterceptor {
    // 无状态,可以安全地在多个代理对象间共享
    override fun invoke(invocation: MethodInvocation): Any? {
        println("开始事务")
        try {
            val result = invocation.proceed()
            println("提交事务")
            return result
        } catch (e: Exception) {
            println("回滚事务")
            throw e
        }
    }
}
kotlin
class CounterAdvice : MethodInterceptor {
    private var callCount = 0 // 有状态,需要每个代理对象独立实例
    
    override fun invoke(invocation: MethodInvocation): Any? {
        callCount++
        println("第 $callCount 次调用方法: ${invocation.method.name}")
        return invocation.proceed()
    }
}

TIP

选择策略的经验法则

  • 如果 Advice 是无状态的(如事务管理、日志记录),选择 Per-Class
  • 如果 Advice 需要维护状态(如计数器、缓存),选择 Per-Instance

Spring Advice 类型详解 📋

1. Around Advice (环绕通知) - 最强大的通知类型

Around Advice 是功能最完整的通知类型,它可以完全控制目标方法的执行流程。

kotlin
@Component
class PerformanceMonitorAdvice : MethodInterceptor {
    
    override fun invoke(invocation: MethodInvocation): Any? {
        val startTime = System.currentTimeMillis()
        val methodName = invocation.method.name
        
        println("🚀 开始执行方法: $methodName")
        
        return try {
            // 调用目标方法 - 这是关键!
            val result = invocation.proceed() 
            
            val endTime = System.currentTimeMillis()
            println("✅ 方法 $methodName 执行成功,耗时: ${endTime - startTime}ms")
            
            result
        } catch (e: Exception) {
            val endTime = System.currentTimeMillis()
            println("❌ 方法 $methodName 执行失败,耗时: ${endTime - startTime}ms")
            throw e
        }
    }
}

IMPORTANT

invocation.proceed() 是 Around Advice 的核心。如果不调用它,目标方法将不会执行!这给了我们完全的控制权,但也要求我们必须负责任地使用这个能力。

2. Before Advice (前置通知) - 简单而高效

Before Advice 在目标方法执行前运行,适合参数验证、权限检查等场景。

kotlin
@Component
class SecurityCheckAdvice : MethodBeforeAdvice {
    
    override fun before(method: Method, args: Array<Any>, target: Any?) {
        // 检查用户权限
        val currentUser = getCurrentUser()
        if (!hasPermission(currentUser, method.name)) {
            throw SecurityException("用户 ${currentUser.name} 无权限访问 ${method.name}") 
        }
        
        // 记录访问日志
        println("🔐 用户 ${currentUser.name} 访问方法: ${method.name}")
    }
    
    private fun getCurrentUser(): User {
        // 从 SecurityContext 获取当前用户
        return SecurityContextHolder.getContext().authentication.principal as User
    }
    
    private fun hasPermission(user: User, methodName: String): Boolean {
        // 权限检查逻辑
        return user.permissions.contains(methodName)
    }
}

WARNING

Before Advice 抛出的异常会阻止目标方法执行。确保异常处理逻辑正确,避免误杀正常请求。

3. After Returning Advice (返回后通知) - 成功后的处理

After Returning Advice 只在方法成功返回时执行,适合结果处理、缓存更新等场景。

kotlin
@Component
class CacheUpdateAdvice : AfterReturningAdvice {
    
    private val cacheManager = CacheManager.getInstance()
    
    override fun afterReturning(
        returnValue: Any?, 
        method: Method, 
        args: Array<Any>, 
        target: Any?
    ) {
        // 只有在方法成功执行后才会到达这里
        when (method.name) {
            "getUserById" -> {
                val userId = args[0] as Long
                cacheManager.put("user:$userId", returnValue) 
                println("📦 缓存已更新: user:$userId")
            }
            "updateUser" -> {
                val user = args[0] as User
                cacheManager.evict("user:${user.id}") 
                println("🗑️ 缓存已清除: user:${user.id}")
            }
        }
    }
}

4. Throws Advice (异常通知) - 异常处理的艺术

Throws Advice 提供了类型安全的异常处理机制,可以针对不同类型的异常执行不同的处理逻辑。

kotlin
@Component
class ExceptionHandlingAdvice : ThrowsAdvice {
    
    // 处理业务异常
    fun afterThrowing(ex: BusinessException) {
        println("💼 业务异常: ${ex.message}")
        // 发送业务告警
        alertService.sendBusinessAlert(ex)
    }
    
    // 处理数据库异常,包含完整的方法信息
    fun afterThrowing(
        method: Method, 
        args: Array<Any>, 
        target: Any, 
        ex: DataAccessException
    ) {
        println("🗄️ 数据库异常在方法 ${method.name} 中发生")
        println("📝 参数: ${args.contentToString()}")
        
        // 记录详细的错误信息
        errorLogger.logDatabaseError(method, args, ex) 
        
        // 尝试降级处理
        fallbackService.handleDatabaseFailure(method.name, args) 
    }
    
    // 处理网络异常
    fun afterThrowing(ex: ConnectException) {
        println("🌐 网络连接异常: ${ex.message}")
        // 触发重试机制
        retryService.scheduleRetry()
    }
}

CAUTION

Throws Advice 中抛出的异常会覆盖原始异常。如果你需要保持原始异常,请确保在处理完成后重新抛出它,或者只抛出 RuntimeException。

5. Introduction Advice (引入通知) - 动态混入的魔法

Introduction Advice 是最特殊的通知类型,它可以为现有对象动态添加新的接口实现。

让我们通过一个实际的审计功能来理解它:

kotlin
interface Auditable {
    fun getCreatedAt(): LocalDateTime?
    fun getUpdatedAt(): LocalDateTime?
    fun markAsUpdated()
}
kotlin
class AuditMixin : DelegatingIntroductionInterceptor(), Auditable {
    
    private var createdAt: LocalDateTime? = null
    private var updatedAt: LocalDateTime? = null
    
    init {
        createdAt = LocalDateTime.now()
    }
    
    override fun getCreatedAt(): LocalDateTime? = createdAt
    
    override fun getUpdatedAt(): LocalDateTime? = updatedAt
    
    override fun markAsUpdated() {
        updatedAt = LocalDateTime.now()
    }
    
    override fun invoke(invocation: MethodInvocation): Any? {
        // 拦截所有 setter 方法,自动更新时间戳
        if (invocation.method.name.startsWith("set")) {
            markAsUpdated() 
            println("🕒 对象已更新: ${LocalDateTime.now()}")
        }
        
        return super.invoke(invocation)
    }
}
kotlin
@Component
class AuditAdvisor : DefaultIntroductionAdvisor(
    AuditMixin(), 
    Auditable::class.java
)

使用示例:

kotlin
@Service
class UserService {
    
    @Autowired
    private lateinit var proxyFactory: ProxyFactory
    
    fun createAuditableUser(): User {
        val user = User("张三", "[email protected]")
        
        // 创建代理,添加审计功能
        proxyFactory.target = user
        proxyFactory.addAdvisor(AuditAdvisor())
        
        val auditableUser = proxyFactory.proxy as User
        
        // 现在这个用户对象同时具备了审计功能!
        (auditableUser as Auditable).let { auditable ->
            println("创建时间: ${auditable.getCreatedAt()}")
            
            // 修改用户信息会自动触发审计
            auditableUser.email = "[email protected]"
            
            println("更新时间: ${auditable.getUpdatedAt()}")
        }
        
        return auditableUser
    }
}

实际应用场景 🏢

场景1:分布式系统中的服务调用监控

kotlin
@Component
class DistributedTracingAdvice : MethodInterceptor {
    
    override fun invoke(invocation: MethodInvocation): Any? {
        val traceId = generateTraceId()
        val spanId = generateSpanId()
        
        // 设置分布式追踪上下文
        TracingContext.setTraceId(traceId)
        TracingContext.setSpanId(spanId)
        
        val startTime = System.nanoTime()
        
        return try {
            println("🔍 [Trace: $traceId] 开始调用: ${invocation.method.name}")
            
            val result = invocation.proceed()
            
            val duration = (System.nanoTime() - startTime) / 1_000_000
            println("✅ [Trace: $traceId] 调用成功,耗时: ${duration}ms")
            
            // 上报监控数据
            reportMetrics(invocation.method.name, duration, "SUCCESS") 
            
            result
        } catch (e: Exception) {
            val duration = (System.nanoTime() - startTime) / 1_000_000
            println("❌ [Trace: $traceId] 调用失败: ${e.message}")
            
            // 上报错误监控数据
            reportMetrics(invocation.method.name, duration, "ERROR") 
            
            throw e
        } finally {
            TracingContext.clear()
        }
    }
    
    private fun reportMetrics(methodName: String, duration: Long, status: String) {
        // 发送到监控系统(如 Prometheus、Grafana)
        metricsCollector.recordMethodCall(methodName, duration, status)
    }
}

场景2:智能缓存管理

kotlin
@Component
class SmartCacheAdvice : MethodInterceptor {
    
    private val cache = ConcurrentHashMap<String, CacheEntry>()
    
    override fun invoke(invocation: MethodInvocation): Any? {
        val method = invocation.method
        val cacheKey = generateCacheKey(method, invocation.arguments)
        
        // 检查缓存注解
        val cacheable = method.getAnnotation(Cacheable::class.java)
        if (cacheable != null) {
            return handleCacheableMethod(invocation, cacheKey, cacheable)
        }
        
        val cacheEvict = method.getAnnotation(CacheEvict::class.java)
        if (cacheEvict != null) {
            return handleCacheEvictMethod(invocation, cacheEvict)
        }
        
        return invocation.proceed()
    }
    
    private fun handleCacheableMethod(
        invocation: MethodInvocation, 
        cacheKey: String, 
        cacheable: Cacheable
    ): Any? {
        // 检查缓存
        val cachedEntry = cache[cacheKey]
        if (cachedEntry != null && !cachedEntry.isExpired()) {
            println("🎯 缓存命中: $cacheKey")
            return cachedEntry.value
        }
        
        // 缓存未命中,执行方法
        println("💾 缓存未命中,执行方法: ${invocation.method.name}")
        val result = invocation.proceed()
        
        // 存入缓存
        cache[cacheKey] = CacheEntry(result, cacheable.ttl) 
        
        return result
    }
    
    private fun handleCacheEvictMethod(invocation: MethodInvocation, cacheEvict: CacheEvict): Any? {
        val result = invocation.proceed()
        
        // 清除相关缓存
        if (cacheEvict.allEntries) {
            cache.clear()
            println("🗑️ 清除所有缓存")
        } else {
            val pattern = cacheEvict.key
            cache.keys.filter { it.contains(pattern) }.forEach { 
                cache.remove(it)
                println("🗑️ 清除缓存: $it")
            }
        }
        
        return result
    }
}

最佳实践与注意事项 ⚡

1. 性能优化建议

性能优化要点

  • 优先使用 Before/After Advice:如果不需要控制方法执行流程,避免使用 Around Advice
  • 合理使用缓存:对于频繁调用的 Advice,考虑缓存计算结果
  • 异步处理:对于耗时的日志记录、监控上报等操作,考虑异步执行
kotlin
@Component
class AsyncLoggingAdvice : MethodInterceptor {
    
    private val executor = Executors.newFixedThreadPool(2)
    
    override fun invoke(invocation: MethodInvocation): Any? {
        val startTime = System.currentTimeMillis()
        
        return try {
            val result = invocation.proceed()
            
            // 异步记录成功日志 
            executor.submit { 
                val duration = System.currentTimeMillis() - startTime
                logSuccess(invocation.method.name, duration)
            }
            
            result
        } catch (e: Exception) {
            // 异步记录错误日志
            executor.submit { 
                val duration = System.currentTimeMillis() - startTime
                logError(invocation.method.name, duration, e)
            }
            throw e
        }
    }
}

2. 异常处理最佳实践

异常处理注意事项

  • Before Advice:抛出异常会阻止方法执行
  • After Returning Advice:抛出异常会覆盖方法的正常返回值
  • Throws Advice:抛出新异常会覆盖原始异常
  • Around Advice:必须正确处理 proceed() 的异常

3. 内存泄漏防护

kotlin
@Component
class ResourceManagementAdvice : MethodInterceptor {
    
    private val resources = ThreadLocal<MutableList<AutoCloseable>>()
    
    override fun invoke(invocation: MethodInvocation): Any? {
        resources.set(mutableListOf())
        
        return try {
            invocation.proceed()
        } finally {
            // 确保资源清理
            resources.get()?.forEach { resource ->
                try {
                    resource.close() 
                } catch (e: Exception) {
                    println("资源清理失败: ${e.message}")
                }
            }
            resources.remove() 
        }
    }
}

总结 🎉

Spring AOP 的 Advice API 为我们提供了强大而灵活的横切关注点处理能力:

  • Around Advice 提供最大的控制权,适合复杂的业务逻辑处理
  • Before/After Advice 简单高效,适合大多数常见场景
  • Throws Advice 提供类型安全的异常处理
  • Introduction Advice 支持动态接口混入,实现运行时功能增强

通过合理选择和组合不同类型的 Advice,我们可以构建出既强大又优雅的企业级应用架构。记住,好的 AOP 设计应该是透明的、高性能的,并且不会给业务代码带来额外的复杂性。

TIP

在实际项目中,建议优先使用 Spring 的注解式 AOP(@Aspect),它在 Advice API 的基础上提供了更简洁的编程模型。但理解底层的 Advice API 对于深入掌握 Spring AOP 仍然非常重要!