Appearance
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)
}
}
执行顺序:
- 安全检查(Order=1 的 @Before)
- 记录日志(Order=2 的 @Before)
- 执行业务逻辑
- 记录返回日志(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 来处理! 🚀