一句话总结
AOP(面向切面编程):将横切关注点(日志、事务、权限)从业务代码中分离,通过动态代理在方法执行前后织入增强逻辑。Spring AOP 默认使用 JDK 动态代理(基于接口)或 CGLIB(基于继承,Spring Boot 2.x 默认)。核心概念:Aspect(切面)= Pointcut(切入点)+ Advice(通知)。通知类型:@Before、@After、@AfterReturning、@AfterThrowing、@Around(最强大,可控制方法执行)。
初级理解
AOP 核心概念
# AOP 术语
# JoinPoint(连接点):所有可以被增强的方法
# Pointcut(切入点):实际被增强的方法(JoinPoint 的子集)
# Advice(通知):增强的逻辑(什么时候做什么)
# Aspect(切面):Pointcut + Advice 的组合
# Target(目标对象):被代理的对象
# Weaving(织入):将增强应用到目标对象的过程
# 通知类型
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() { } // 方法执行前
@After("execution(* com.example.service.*.*(..))")
public void after() { } // 方法执行后(无论是否异常)
@AfterReturning("execution(* com.example.service.*.*(..))")
public void afterReturning() { } // 正常返回后
@AfterThrowing("execution(* com.example.service.*.*(..))")
public void afterThrowing() { } // 异常抛出后
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 前置逻辑
Object result = pjp.proceed(); // 执行目标方法
// 后置逻辑
return result;
}
}
中级深入
JDK 动态代理 vs CGLIB
| 对比维度 | JDK 动态代理 | CGLIB |
|---|---|---|
| 原理 | 基于接口,生成实现类 | 基于继承,生成子类 |
| 要求 | 必须有接口 | 不能是 final 类/方法 |
| 性能 | 创建快,执行略慢 | 创建慢,执行快 |
| Spring 默认 | 有接口时使用 | 无接口时使用(Spring Boot 2.x 默认) |
# Spring Boot 2.x 默认使用 CGLIB
# 配置切换
spring:
aop:
proxy-target-class: false # false = JDK 动态代理
# 为什么默认改为 CGLIB?
# 1. 不需要强制写接口
# 2. 同一个类内部调用也能被代理(通过 this 调用不行)
# 3. 性能更好
切点表达式
# execution 表达式格式
# execution(修饰符 返回值 包名.类名.方法名(参数))
# 常用示例
execution(* com.example.service.*.*(..))
# 任意返回值,service 包下任意类任意方法,任意参数
execution(public * com.example..*.find*(Long))
# public 方法,任意返回值,com.example 及子包,find 开头,Long 参数
# @annotation 切点
@annotation(com.example.annotation.Log)
# 标注了 @Log 注解的方法
# 组合切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("@annotation(com.example.annotation.Log)")
public void logAnnotation() {}
@Around("serviceLayer() && logAnnotation()")
public Object around(ProceedingJoinPoint pjp) { }
高级拓展
@Transactional 失效场景
# 场景1:非 public 方法
@Transactional
private void save() { } // ❌ 失效!AOP 只能代理 public 方法
# 场景2:同类内部调用
@Service
public class UserService {
public void methodA() {
this.methodB(); // ❌ 失效!this 是原始对象,不是代理
}
@Transactional
public void methodB() { }
}
# 解决方案:注入自己
@Service
public class UserService {
@Autowired
private UserService self;
public void methodA() {
self.methodB(); // ✅ 通过代理调用
}
}
# 场景3:异常被捕获
@Transactional
public void save() {
try {
// 数据库操作
} catch (Exception e) {
log.error("error", e); // ❌ 事务不回滚!异常被吞了
}
}
# 场景4:非 RuntimeException(默认不回滚)
@Transactional
public void save() throws Exception {
throw new Exception("error"); // ❌ 默认不回滚 checked 异常
}
# 指定回滚异常
@Transactional(rollbackFor = Exception.class)
实战场景
场景:自定义日志注解
# 1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
String value() default "";
}
# 2. 定义切面
@Aspect
@Component
public class OperationLogAspect {
@Around("@annotation(operationLog)")
public Object around(ProceedingJoinPoint pjp, OperationLog operationLog) {
String desc = operationLog.value();
String method = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
log.info("操作开始:{},方法:{},参数:{}", desc, method, args);
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
log.info("操作成功:{},耗时:{}ms", desc, System.currentTimeMillis() - start);
return result;
} catch (Throwable e) {
log.error("操作失败:{},异常:{}", desc, e.getMessage());
throw new RuntimeException(e);
}
}
}
# 3. 使用
@OperationLog("创建用户")
public User createUser(UserDTO dto) { }
面试模拟
面试官:Spring AOP 的原理是什么?
你:基于动态代理。如果目标类实现了接口,默认用 JDK 动态代理(生成接口实现类);如果没有接口,用 CGLIB(生成子类)。Spring Boot 2.x 默认使用 CGLIB。AOP 将横切逻辑(日志、事务)从业务代码中分离,通过代理对象在方法执行前后织入增强。
面试官:@Transactional 什么情况下会失效?
你:1)非 public 方法;2)同类内部调用(this 调用不走代理);3)异常被 try-catch 吞掉;4)默认只回滚 RuntimeException,checked 异常不回滚;5)数据库引擎不支持事务(如 MyISAM)。