Appearance
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 仍然非常重要!