最近咨询的学员较多,其实这个也是老生常谈的问题,但是往往在面试求职环节,问的居多,尤其是工作1~3年的同学,很多人是会做,说不上来,今天与大家一起聊聊;
我来详细讲讲对Spring AOP的理解,包括概念、实现方式和具体例子。
一、什么是AOP?
AOP(面向切面编程)是一种编程范式,用于将横切关注点(如日志、事务、安全等)与核心业务逻辑分离的技术。
核心思想:
- 关注点分离:将系统的通用功能(横切关注点)从业务逻辑中抽离
- 模块化:通过切面将这些通用功能模块化,便于复用和维护
- 解耦:避免业务代码中混入大量重复的通用代码
二、AOP的核心概念
| 概念 | 说明 | 类比 |
|---|---|---|
| Aspect(切面) | 横切关注点的模块化,如日志切面、事务切面 | 一个专门的"功能模块" |
| Join Point(连接点) | 程序执行过程中的某个点,如方法调用、异常抛出 | 可以被增强的"时机点" |
| Pointcut(切点) | 匹配连接点的表达式,定义在哪些地方应用切面 | "筛选器",选择哪些连接点要增强 |
| Advice(通知) | 在特定连接点执行的动作 | 增强的具体"行为" |
| Weaving(织入) | 将切面应用到目标对象创建代理对象的过程 | "植入"增强逻辑的过程 |
三、Advice的类型
java
// 1. Before:方法执行前
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice() {
System.out.println("方法执行前");
}
// 2. AfterReturning:方法正常返回后
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))",
returning = "result")
public void afterReturning(Object result) {
System.out.println("方法返回: " + result);
}
// 3. AfterThrowing:方法抛出异常后
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex")
public void afterThrowing(Exception ex) {
System.out.println("方法异常: " + ex.getMessage());
}
// 4. After(Finally):方法执行后(无论成功还是异常)
@After("execution(* com.example.service.*.*(..))")
public void afterAdvice() {
System.out.println("方法执行结束");
}
// 5. Around:环绕通知(最强大)
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法执行前
System.out.println("开始执行: " + joinPoint.getSignature());
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 执行目标方法
// 方法执行后
long end = System.currentTimeMillis();
System.out.println("执行耗时: " + (end - start) + "ms");
return result;
}
四、具体例子:日志切面
场景:为所有Service方法添加执行日志和耗时统计
java
@Component
@Aspect
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
// 定义切点:匹配所有Service包下的方法
@Pointcut("execution(* com.example.service..*.*(..))")
public void serviceLayer() {}
// 环绕通知:记录方法执行耗时
@Around("serviceLayer()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
try {
logger.info("[开始执行] {}.{}() 参数: {}",
className, methodName, Arrays.toString(joinPoint.getArgs()));
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
logger.info("[执行成功] {}.{}() 耗时: {}ms 结果: {}",
className, methodName, (end - start), result);
return result;
} catch (Exception e) {
long end = System.currentTimeMillis();
logger.error("[执行异常] {}.{}() 耗时: {}ms 异常: {}",
className, methodName, (end - start), e.getMessage(), e);
throw e;
}
}
// 方法执行前的通知
@Before("serviceLayer() && @annotation(org.springframework.transaction.annotation.Transactional)")
public void logBeforeTransactionalMethod(JoinPoint joinPoint) {
logger.debug("准备执行事务方法: {}", joinPoint.getSignature());
}
}
五、实际应用场景
1. 事务管理(Spring最经典的AOP应用)
java
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 业务逻辑
orderDao.save(order);
inventoryService.reduceStock(order);
// 如果出现异常,整个事务会回滚
}
}
2. 权限控制
java
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequiresPermission annotation = signature.getMethod()
.getAnnotation(RequiresPermission.class);
String permission = annotation.value();
if (!SecurityContext.hasPermission(permission)) {
throw new UnauthorizedException("缺少权限: " + permission);
}
}
}
// 使用
@RequiresPermission("order:create")
public void createOrder(Order order) {
// 需要权限的业务逻辑
}
3. 缓存切面
java
@Aspect
@Component
public class CacheAspect {
@Around("@annotation(cacheable)")
public Object cacheResult(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String key = generateCacheKey(joinPoint);
Object cachedValue = cache.get(key);
if (cachedValue != null) {
return cachedValue;
}
Object result = joinPoint.proceed();
cache.put(key, result, cacheable.ttl());
return result;
}
}
六、Spring AOP的实现原理
底层技术:
- JDK动态代理(默认,针对接口)
- CGLIB动态代理(针对类,当目标对象没有实现接口时)
织入时机:
- 编译时织入(AspectJ)
- 类加载时织入(AspectJ)
- 运行时织入(Spring AOP采用的方式)
七、使用建议
优点:
✅ 减少重复代码 :横切关注点只需编写一次 ✅ 提高可维护性 :业务逻辑更清晰 ✅ 灵活配置 :通过注解或XML配置切面 ✅ 易于测试:切面可以独立测试
注意事项:
⚠️ 性能开销 :代理调用比直接调用稍慢 ⚠️ 仅方法拦截 :Spring AOP只能拦截方法,不能拦截字段访问 ⚠️ 自调用问题 :同一类内方法互相调用,AOP可能失效 ⚠️ 理解复杂度:需要理解代理机制和AOP概念
总结
Spring AOP通过动态代理技术,在不修改原有代码的情况下,为程序添加额外的功能。它特别适合处理横切关注点,如日志、事务、安全、缓存等。理解AOP的核心概念(切面、切点、通知)和实际应用场景,能帮助我们编写更清晰、更易维护的代码。