Spring AOP 详解
一、Spring AOP 是什么?
Spring AOP(面向切面编程) 是 Spring 框架的核心模块之一,旨在通过动态代理技术,将横切关注点(如日志、事务、权限校验)与核心业务逻辑分离,提升代码的模块化和可维护性。
- 核心思想:将分散在多个类中的重复代码(横切逻辑)提取到独立的模块(切面),通过声明式配置将其“织入”到目标方法中。
- 与 AspectJ 的关系:Spring AOP 是简化版的 AOP 实现,基于动态代理,适用于大多数常见场景;AspectJ 是更强大的 AOP 框架,支持编译时织入和更复杂的切入点表达式。
二、环境配置与依赖
1. 导入依赖
在 Maven 项目中,需添加以下依赖(Spring Boot 项目推荐使用 spring-boot-starter-aop
):
<!-- Spring Boot AOP 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
三、核心概念详解
1. 切面(Aspect)
- 定义:封装横切逻辑的模块(如日志切面、事务切面)。
- 实现:使用
@Aspect
注解标记类为切面,并通过@Component
纳入 Spring 容器管理。
@Aspect
@Component
public class LogAspect {
// 切入点和通知在此定义
}
2. 连接点(Join Point)
- 定义:程序执行过程中的某个点(如方法调用、异常抛出、字段访问)。
- 常见类型:
- 方法执行(
Method execution
) - 方法调用(
Method call
) - 异常处理(
Exception handling
)
- 方法执行(
3. 切入点(Pointcut)
- 定义:通过表达式匹配哪些连接点需要被切面处理指定执行的位置。
- 语法:使用
execution
表达式或注解匹配。
切入点表达式示例
// 匹配 com.example.service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 匹配所有带有 @Transactional 注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
4. 通知(Advice)
- 定义:切面在特定连接点执行的具体逻辑。
- 类型:
通知类型 | 注解 | 执行时机 |
---|---|---|
前置通知 | @Before | 目标方法执行前 |
后置通知 | @After | 目标方法执行后(无论是否异常) |
返回通知 | @AfterReturning | 目标方法正常返回后 |
异常通知 | @AfterThrowing | 目标方法抛出异常后 |
环绕通知 | @Around | 包裹目标方法,可控制是否执行方法 |
通知示例
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 前置通知
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("执行方法: " + methodName);
}
// 环绕通知(记录方法耗时)
@Around("serviceMethods()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
long duration = System.currentTimeMillis() - start;
System.out.println("方法耗时: " + duration + "ms");
return result;
}
}
四、自定义注解结合Spring AOP(通过注解可以指定在那些位置执行AOP的代码)
1. 定义自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceMonitor {}
2. 定义切面
@Aspect
@Component
public class PerformanceAspect {
// 切入点:匹配所有带 @PerformanceMonitor 注解的方法
@Pointcut("@annotation(com.example.annotation.PerformanceMonitor)")
public void monitoredMethods() {}
// 环绕通知
@Around("monitoredMethods()")
public Object measurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("方法执行耗时: " + (endTime - startTime) + "ms");
return result;
}
}
3. 在业务类中使用注解
@Service
public class UserService {
@PerformanceMonitor
public void processUsers() {
// 业务逻辑
System.out.println("处理用户数据...");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 测试代码
@SpringBootTest
public class AopTest {
@Autowired
private UserService userService;
@Test
public void testPerformanceAspect() {
userService.processUsers();
}
}
输出结果:
处理用户数据...
方法执行耗时: 1001ms
多个aop切面的时候,通知顺序
五、底层原理:动态代理
Spring AOP 通过 动态代理 实现切面逻辑的织入:
- JDK 动态代理:基于接口,要求目标类必须实现至少一个接口。
- CGLIB 代理:基于类,通过生成目标类的子类实现代理(无需接口)。
代理选择规则:
- 若目标类实现了接口,默认使用 JDK 动态代理。
- 若目标类未实现接口,使用 CGLIB 代理。
- 可通过
@EnableAspectJAutoProxy(proxyTargetClass = true)
强制使用 CGLIB。
六、总结
Spring AOP 的核心价值:
- 解耦:将横切逻辑与业务代码分离。
- 复用:通过切面集中管理通用逻辑。
- 灵活:通过注解和表达式精准控制切入范围。
适用场景:
- 日志记录
- 事务管理(如
@Transactional
) - 权限校验
- 性能监控
- 异常统一处理
掌握 Spring AOP 是构建高可维护性企业级应用的关键!