Skip to content

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 能力:

  1. @Configurable 让领域对象也能享受依赖注入
  2. 加载时织入 提供了更强大的拦截能力
  3. 事务管理 支持更细粒度的控制
  4. 性能监控 可以无侵入地添加到任何对象

何时使用 AspectJ?

  • 需要拦截非 Spring 管理的对象时
  • 需要拦截字段访问、构造器调用时
  • 需要更高性能的 AOP 实现时
  • 在领域驱动设计中需要富领域模型时

通过合理使用 Spring AspectJ 集成,我们可以构建更加灵活、强大的应用程序架构! 🎯