Appearance
Spring AspectJ 集成:让 AOP 更强大 🚀
概述:为什么需要 AspectJ?
在前面的章节中,我们学习了 Spring AOP 的强大功能。但有时候,Spring AOP 的能力可能还不够满足我们的需求。这时候,AspectJ 就登场了!
NOTE
Spring AOP 基于代理模式,只能拦截 Spring 容器管理的 Bean 的方法调用。而 AspectJ 是一个完整的 AOP 框架,可以在字节码级别进行织入,功能更加强大。
AspectJ vs Spring AOP
- Spring AOP:基于代理,只能拦截方法调用,仅支持 Spring Bean
- AspectJ:基于字节码织入,支持字段访问、构造器调用等,可以拦截任何 Java 对象
核心概念:依赖注入领域对象
问题场景
想象一下这个场景:你有一个 Account
领域对象,它不是由 Spring 容器创建的(比如通过 new
操作符或 ORM 工具创建),但你希望它也能享受到 Spring 的依赖注入功能。
解决方案:@Configurable 注解
Spring 提供了 @Configurable
注解来解决这个问题:
kotlin
package com.xyz.domain
import org.springframework.beans.factory.annotation.Configurable
import org.springframework.beans.factory.annotation.Autowired
@Configurable
class Account {
@Autowired
private lateinit var fundsTransferService: FundsTransferService
fun transfer(amount: BigDecimal, targetAccount: String) {
// 现在可以使用注入的服务了!
fundsTransferService.transfer(this.accountNumber, targetAccount, amount)
}
}
kotlin
// 传统方式:需要手动获取依赖
class Account {
fun transfer(amount: BigDecimal, targetAccount: String) {
// 硬编码依赖,难以测试和维护
val service = ServiceLocator.getFundsTransferService()
service.transfer(this.accountNumber, targetAccount, amount)
}
}
Spring 配置
需要在 Spring 配置中启用 @Configurable
支持:
kotlin
@Configuration
@EnableSpringConfigured
class ApplicationConfiguration {
@Bean
fun fundsTransferService(): FundsTransferService {
return FundsTransferServiceImpl()
}
// 为 @Configurable 对象定义原型Bean
@Bean
@Scope("prototype")
fun account(): Account {
return Account()
}
}
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context">
<!-- 启用@Configurable支持 -->
<context:spring-configured />
<!-- 业务服务 -->
<bean id="fundsTransferService"
class="com.xyz.service.FundsTransferServiceImpl"/>
<!-- 为Account定义原型Bean -->
<bean class="com.xyz.domain.Account" scope="prototype">
<property name="fundsTransferService" ref="fundsTransferService"/>
</bean>
</beans>
工作原理
IMPORTANT
@Configurable
的魔法在于 AnnotationBeanConfigurerAspect
切面。当创建标注了 @Configurable
的对象时,这个切面会自动使用 Spring 容器来配置新创建的对象。
高级配置选项
自定义Bean名称
kotlin
@Configurable("customAccountBean")
class Account {
// Spring 会查找名为 "customAccountBean" 的Bean定义
}
自动装配模式
kotlin
// 按类型自动装配
@Configurable(autowire = Autowire.BY_TYPE)
class Account {
// 不需要显式的@Autowired注解
private lateinit var fundsTransferService: FundsTransferService
}
// 按名称自动装配
@Configurable(autowire = Autowire.BY_NAME)
class Account {
// 属性名必须与Bean名称匹配
private lateinit var fundsTransferService: FundsTransferService
}
构造器前注入
kotlin
@Configurable(preConstruction = true)
class Account {
@Autowired
private lateinit var fundsTransferService: FundsTransferService
init {
// 现在可以在构造器中使用注入的依赖!
fundsTransferService.validateAccount(this)
}
}
WARNING
使用 preConstruction = true
时要小心,因为这会改变对象初始化的语义。确保你真的需要在构造器中使用依赖。
事务管理支持
Spring AspectJ 还提供了强大的事务管理支持:
kotlin
@Transactional
class AccountService {
@Transactional(readOnly = true)
fun getAccountBalance(accountId: String): BigDecimal {
// 只读事务
return accountRepository.findBalance(accountId)
}
@Transactional(rollbackFor = [InsufficientFundsException::class])
fun transfer(from: String, to: String, amount: BigDecimal) {
// 遇到InsufficientFundsException时回滚
accountRepository.debit(from, amount)
accountRepository.credit(to, amount)
}
}
TIP
与 Spring AOP 不同,AspectJ 的 @Transactional
支持可以拦截私有方法和非 Spring 管理的对象!
加载时织入(Load-Time Weaving)
什么是加载时织入?
加载时织入是指在类被 JVM 加载时动态地将切面代码织入到目标类中。这比编译时织入更灵活,比运行时代理更强大。
配置示例
1. 创建性能监控切面
kotlin
@Aspect
class ProfilingAspect {
@Around("methodsToBeProfiled()")
fun profile(pjp: ProceedingJoinPoint): Any? {
val sw = StopWatch(javaClass.simpleName)
try {
sw.start(pjp.signature.name)
return pjp.proceed()
} finally {
sw.stop()
println(sw.prettyPrint())
}
}
@Pointcut("execution(public * com.xyz..*.*(..))")
fun methodsToBeProfiled() {}
}
2. 配置 META-INF/aop.xml
xml
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN"
"https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<!-- 只织入应用包下的类 -->
<include within="com.xyz..*"/>
</weaver>
<aspects>
<!-- 指定要织入的切面 -->
<aspect name="com.xyz.ProfilingAspect"/>
</aspects>
</aspectj>
3. Spring 配置
kotlin
@Configuration
@EnableLoadTimeWeaving
class ApplicationConfiguration {
@Bean
fun businessService(): BusinessService {
return BusinessServiceImpl()
}
}
4. 启动应用
bash
# 使用 Spring 提供的 Java Agent
java -javaagent:/path/to/spring-instrument.jar com.xyz.Main
运行效果
Calculating entitlement
StopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms % Task name
------ ----- ----------------------------
01234 100% calculateEntitlement
NOTE
加载时织入的强大之处在于,即使是通过 new
操作符创建的对象,也能被切面拦截!
实际应用场景
1. 领域驱动设计(DDD)
kotlin
@Configurable
@Entity
class Order {
@Autowired
private lateinit var domainEventPublisher: DomainEventPublisher
@Autowired
private lateinit var orderValidator: OrderValidator
fun confirm() {
orderValidator.validate(this)
this.status = OrderStatus.CONFIRMED
domainEventPublisher.publish(OrderConfirmedEvent(this))
}
}
2. 审计日志
kotlin
@Aspect
class AuditAspect {
@Autowired
private lateinit var auditService: AuditService
@After("@annotation(Auditable)")
fun audit(joinPoint: JoinPoint) {
val method = joinPoint.signature.name
val args = joinPoint.args
auditService.log("Method $method called with args: ${args.contentToString()}")
}
}
// 使用
class UserService {
@Auditable
fun deleteUser(userId: String) {
// 删除用户逻辑
// 方法执行后会自动记录审计日志
}
}
3. 缓存管理
kotlin
@Aspect
class CacheAspect {
@Around("@annotation(Cacheable)")
fun cache(pjp: ProceedingJoinPoint): Any? {
val key = generateKey(pjp)
return cacheManager.get(key) ?: run {
val result = pjp.proceed()
cacheManager.put(key, result)
result
}
}
}
最佳实践
1. 选择合适的织入方式
选择指南
- 编译时织入:性能最好,但需要特殊的编译器
- 加载时织入:平衡性能和灵活性,推荐用于生产环境
- 运行时织入:最灵活,但性能开销较大
2. 合理使用 @Configurable
kotlin
// ✅ 好的实践
@Configurable
class DomainObject {
@Autowired
private lateinit var domainService: DomainService // 注入领域服务
// 领域逻辑
}
// ❌ 避免的实践
@Configurable
class SimpleDataObject {
@Autowired
private lateinit var jdbcTemplate: JdbcTemplate // 不要在简单数据对象中注入基础设施
}
3. 测试策略
kotlin
class AccountTest {
@Test
fun `should transfer funds successfully`() {
// 在测试中,@Configurable 注解不会生效
// 可以手动设置依赖或使用 Mock
val account = Account().apply {
fundsTransferService = mockk<FundsTransferService>()
}
every { account.fundsTransferService.transfer(any(), any(), any()) } returns Unit
account.transfer(BigDecimal("100"), "target-account")
verify { account.fundsTransferService.transfer(any(), any(), any()) }
}
}
总结
Spring AspectJ 集成为我们提供了强大的 AOP 能力:
- @Configurable 让领域对象也能享受依赖注入
- 加载时织入 提供了更强大的拦截能力
- 事务管理 支持更细粒度的控制
- 性能监控 可以无侵入地添加到任何对象
何时使用 AspectJ?
- 需要拦截非 Spring 管理的对象时
- 需要拦截字段访问、构造器调用时
- 需要更高性能的 AOP 实现时
- 在领域驱动设计中需要富领域模型时
通过合理使用 Spring AspectJ 集成,我们可以构建更加灵活、强大的应用程序架构! 🎯