Appearance
Spring AOP 切面实例化模型详解 ⚙️
概述
在 Spring AOP 的世界里,切面(Aspect)的实例化模型决定了切面实例的生命周期和作用范围。理解这些模型对于构建高效、可维护的 AOP 应用至关重要。
NOTE
这是一个高级主题。如果你刚开始学习 AOP,可以先跳过这部分内容,等有了基础后再回来学习。
为什么需要不同的实例化模型? 🤔
想象一下这样的场景:
- 单例模式的局限:默认情况下,Spring 中每个切面都是单例的,这意味着所有被代理的对象共享同一个切面实例
- 状态管理问题:如果切面需要维护特定于某个对象的状态,单例模式就无法满足需求
- 性能优化需求:有时我们希望为不同的对象或类型创建独立的切面实例,以实现更精细的控制
支持的实例化模型 📦
Spring 支持以下 AspectJ 实例化模型:
模型 | 描述 | 支持状态 |
---|---|---|
singleton | 默认模式,应用上下文中只有一个切面实例 | ✅ 支持 |
perthis | 为每个匹配的代理对象创建一个切面实例 | ✅ 支持 |
pertarget | 为每个匹配的目标对象创建一个切面实例 | ✅ 支持 |
pertypewithin | 为每个匹配的类型创建一个切面实例 | ✅ 支持 |
percflow | 基于控制流的实例化 | ❌ 暂不支持 |
percflowbelow | 基于控制流下方的实例化 | ❌ 暂不支持 |
perthis 实例化模型深度解析 🔍
核心概念
perthis
模型为每个唯一的代理对象创建一个切面实例。这里的关键是理解"代理对象"的概念。
工作原理
实际应用示例
kotlin
@Aspect("perthis(execution(* com.example.service.*.*(..)))")
@Component
class ServiceUsageTracker {
// 每个服务对象都有独立的调用计数器
private var callCount: Int = 0
private val startTime: Long = System.currentTimeMillis()
@Before("execution(* com.example.service.*.*(..))")
fun trackServiceCall(joinPoint: JoinPoint) {
callCount++
println("服务 ${joinPoint.target.javaClass.simpleName} 第 $callCount 次调用")
}
@After("execution(* com.example.service.*.*(..))")
fun logServiceStats() {
val uptime = System.currentTimeMillis() - startTime
println("当前服务实例运行时间: ${uptime}ms, 总调用次数: $callCount")
}
}
kotlin
@Service
class UserService {
fun createUser(name: String): String {
println("创建用户: $name")
return "用户 $name 创建成功"
}
fun deleteUser(id: Long) {
println("删除用户: $id")
}
}
@Service
class OrderService {
fun createOrder(userId: Long): String {
println("创建订单,用户ID: $userId")
return "订单创建成功"
}
}
kotlin
@SpringBootTest
class PerThisAspectTest {
@Autowired
private lateinit var userService: UserService
@Autowired
private lateinit var orderService: OrderService
@Test
fun testPerThisInstantiation() {
// UserService 的代理对象将获得独立的切面实例
userService.createUser("张三")
userService.deleteUser(1L)
// OrderService 的代理对象将获得另一个独立的切面实例
orderService.createOrder(1L)
// 再次调用 UserService,使用相同的切面实例
userService.createUser("李四")
}
}
输出结果分析
服务 UserService 第 1 次调用
创建用户: 张三
当前服务实例运行时间: 5ms, 总调用次数: 1
服务 UserService 第 2 次调用
删除用户: 1
当前服务实例运行时间: 8ms, 总调用次数: 2
服务 OrderService 第 1 次调用 // [!code highlight]
创建订单,用户ID: 1
当前服务实例运行时间: 2ms, 总调用次数: 1
服务 UserService 第 3 次调用
创建用户: 李四
当前服务实例运行时间: 12ms, 总调用次数: 3
IMPORTANT
注意观察:UserService
和 OrderService
各自维护独立的调用计数器和运行时间,这证明了 perthis
模型为每个服务代理对象创建了独立的切面实例。
pertarget 实例化模型 🎯
与 perthis 的区别
pertarget
模型的工作方式与 perthis
完全相同,但关注点不同:
- perthis:关注代理对象(
this
) - pertarget:关注目标对象(
target
)
在大多数情况下,这两种模型的行为是相同的,因为代理对象和目标对象通常是一一对应的。
使用场景对比
kotlin
@Aspect("pertarget(execution(* com.example.repository.*.*(..)))")
@Component
class DatabaseConnectionTracker {
private val connectionPool: MutableSet<String> = mutableSetOf()
@Before("execution(* com.example.repository.*.*(..))")
fun trackConnection(joinPoint: JoinPoint) {
val targetClass = joinPoint.target.javaClass.simpleName
connectionPool.add("连接-${targetClass}-${System.currentTimeMillis()}")
println("$targetClass 当前连接池大小: ${connectionPool.size}")
}
}
kotlin
@Repository
class UserRepository {
fun findById(id: Long): String = "用户数据-$id"
}
@Repository
class OrderRepository {
fun findByUserId(userId: Long): List<String> = listOf("订单1", "订单2")
}
实际业务场景应用 💼
场景1:API 调用频率限制
kotlin
@Aspect("perthis(execution(* com.example.api.*.*(..)))")
@Component
class RateLimitAspect {
private val callTimestamps: MutableList<Long> = mutableListOf()
private val maxCallsPerMinute = 100
@Before("execution(* com.example.api.*.*(..))")
fun checkRateLimit(joinPoint: JoinPoint) {
val now = System.currentTimeMillis()
val oneMinuteAgo = now - 60_000
// 清理过期的时间戳
callTimestamps.removeIf { it < oneMinuteAgo }
if (callTimestamps.size >= maxCallsPerMinute) {
throw RuntimeException("API调用频率超限,请稍后再试")
}
callTimestamps.add(now)
println("API ${joinPoint.signature.name} 当前分钟内调用次数: ${callTimestamps.size}")
}
}
场景2:缓存管理
kotlin
@Aspect("perthis(execution(* com.example.cache.*.get*(..)))")
@Component
class CacheStatsAspect {
private var hitCount: Long = 0
private var missCount: Long = 0
@AfterReturning(
pointcut = "execution(* com.example.cache.*.get*(..))",
returning = "result"
)
fun recordCacheHit(result: Any?) {
if (result != null) {
hitCount++
} else {
missCount++
}
val hitRate = if (hitCount + missCount > 0) {
hitCount.toDouble() / (hitCount + missCount) * 100
} else 0.0
println("缓存命中率: ${"%.2f".format(hitRate)}% (命中: $hitCount, 未命中: $missCount)")
}
}
最佳实践与注意事项 💡
何时使用不同的实例化模型
TIP
选择指南
- singleton:切面无状态或状态全局共享时使用
- perthis/pertarget:需要为每个对象维护独立状态时使用
- pertypewithin:需要为每个类型维护独立状态时使用
性能考虑
WARNING
性能影响
- 非单例模式会创建更多的切面实例,增加内存消耗
- 实例创建和垃圾回收的开销需要考虑
- 在高并发场景下要特别注意性能测试
内存管理
CAUTION
内存泄漏风险
- 切面实例的生命周期与目标对象绑定
- 确保切面中的状态不会无限增长
- 及时清理过期数据,避免内存泄漏
总结 🎉
Spring AOP 的切面实例化模型为我们提供了灵活的切面生命周期管理方式:
- 默认单例模式适用于无状态或全局状态的场景
- perthis/pertarget 模式适用于需要为每个对象维护独立状态的场景
- 合理选择模型可以在功能实现和性能之间找到最佳平衡点
通过理解这些实例化模型,你可以构建更加精细和高效的 AOP 应用,为复杂的业务需求提供优雅的解决方案。
NOTE
记住:选择合适的实例化模型不仅关乎功能实现,更关乎应用的性能和可维护性。在实际项目中,建议先从简单的单例模式开始,根据具体需求再考虑使用更复杂的实例化模型。