一、AOP 核心思想(一句话理解)
传统编程: 业务逻辑 + 横切逻辑混在一起
AOP 编程: 业务逻辑与横切逻辑分离,通过配置动态织入
核心价值
-
解耦:日志、事务、安全等横切关注点与业务逻辑分离
-
复用:通用功能(如性能监控)可多处复用
-
维护:修改横切逻辑只需改一处
二、AOP 核心概念(5分钟掌握)
| 概念 | 通俗理解 | 示例 |
|---|---|---|
| 连接点 | 所有可被增强的方法 | UserService 的所有方法 |
| 切入点 | 实际要增强的方法 | 只增强 save 和 update 方法 |
| 通知 | 增强的具体逻辑 | 记录日志、计算耗时 |
| 切面 | 通知 + 切入点的绑定关系 | 哪些方法需要记录日志 |
| 代理对象 | 增强后的对象 | Spring 动态生成的对象 |
三、AOP 快速入门(10分钟上手)
3.1 基础配置
xml
<!-- 必须的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
3.2 实战:方法执行时间监控
java
// 1. 开启AOP支持
@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy // 🔑 关键注解
public class AppConfig {
}
// 2. 定义切面
@Aspect
@Component
public class PerformanceAspect {
// 3. 定义切入点:所有Service方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
// 4. 环绕通知:计算方法执行时间
@Around("serviceMethods()")
public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标方法
Object result = pjp.proceed();
long end = System.currentTimeMillis();
System.out.println(pjp.getSignature() + " 执行耗时: " + (end - start) + "ms");
return result;
}
}
四、AOP 核心配置(实战重点)
4.1 切入点表达式速查
| 需求 | 表达式 | 说明 |
|---|---|---|
| 特定方法 | execution(void UserService.save()) | 精确匹配 |
| 包下所有方法 | execution(* com.example.service.*.*(..)) | service包所有类方法 |
| 所有Service方法 | execution(* *..*Service.*(..)) | 类名以Service结尾 |
| 查询方法 | execution(* *..*.find*(..)) | 方法名以find开头 |
常用通配符:
-
*:匹配单个元素 -
..:匹配多个元素(包路径或参数) -
+:匹配子类
4.2 5种通知类型对比
| 通知类型 | 注解 | 执行时机 | 适用场景 |
|---|---|---|---|
| 前置通知 | @Before | 方法执行前 | 参数校验、权限检查 |
| 后置通知 | @After | 方法执行后(无论成败) | 资源清理 |
| 返回通知 | @AfterReturning | 方法正常返回后 | 记录成功日志 |
| 异常通知 | @AfterThrowing | 方法抛出异常后 | 异常处理、告警 |
| 环绕通知 | @Around | 方法执行前后 | 性能监控、事务管理 |
4.3 环绕通知实战(最强通知)
java
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 1. 前置处理
System.out.println("方法开始: " + pjp.getSignature().getName());
Object[] args = pjp.getArgs();
try {
// 2. 执行目标方法(可修改参数)
Object result = pjp.proceed(args);
// 3. 返回后处理(可修改返回值)
System.out.println("方法正常返回");
return result;
} catch (Exception e) {
// 4. 异常处理
System.out.println("方法异常: " + e.getMessage());
throw e;
} finally {
// 5. 最终处理
System.out.println("方法执行结束");
}
}
五、AOP 数据获取(实战技巧)
5.1 获取方法参数
java
@Before("serviceMethods()")
public void logParameters(JoinPoint jp) {
Object[] args = jp.getArgs();
String methodName = jp.getSignature().getName();
System.out.println(methodName + " 方法参数: " + Arrays.toString(args));
}
5.2 获取返回值
java
@AfterReturning(
pointcut = "serviceMethods()",
returning = "result" // 🔑 参数名必须一致
)
public void logResult(Object result) {
System.out.println("方法返回值: " + result);
}
5.3 获取异常信息
java
@AfterThrowing(
pointcut = "serviceMethods()",
throwing = "ex" // 🔑 参数名必须一致
)
public void handleException(Exception ex) {
System.out.println("捕获异常: " + ex.getMessage());
// 发送告警、记录错误日志等
}
六、AOP 实战案例
6.1 统一日志记录
java
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Before("serviceLayer()")
public void logMethodStart(JoinPoint jp) {
String className = jp.getTarget().getClass().getSimpleName();
String methodName = jp.getSignature().getName();
log.info("===> {}.{} 开始执行", className, methodName);
log.info("参数: {}", Arrays.toString(jp.getArgs()));
}
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logMethodReturn(JoinPoint jp, Object result) {
String methodName = jp.getSignature().getName();
log.info("<=== {} 执行完成, 返回值: {}", methodName, result);
}
}
6.2 接口性能监控
java
@Aspect
@Component
public class PerformanceAspect {
private static final long SLOW_THRESHOLD = 1000; // 1秒
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public Object monitorApiPerformance(ProceedingJoinPoint pjp) throws Throwable {
String apiName = pjp.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long cost = System.currentTimeMillis() - startTime;
if (cost > SLOW_THRESHOLD) {
log.warn("API {} 执行缓慢, 耗时: {}ms", apiName, cost);
} else {
log.info("API {} 执行耗时: {}ms", apiName, cost);
}
return result;
} catch (Exception e) {
log.error("API {} 执行异常", apiName, e);
throw e;
}
}
}
七、声明式事务管理(企业级必备)
7.1 事务配置三步曲
java
// 1. 配置事务管理器
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 2. 开启事务支持
@Configuration
@EnableTransactionManagement // 🔑 关键注解
public class AppConfig {
}
// 3. 使用事务注解
@Service
public class UserService {
@Transactional // 🔑 方法级事务控制
public void transferMoney(String from, String to, BigDecimal amount) {
// 转账业务逻辑
accountDao.deduct(from, amount);
accountDao.add(to, amount);
}
}
7.2 事务属性配置
java
@Service
public class OrderService {
@Transactional(
readOnly = false, // 读写事务
timeout = 30, // 30秒超时
rollbackFor = Exception.class, // 所有异常都回滚
isolation = Isolation.DEFAULT, // 数据库默认隔离级别
propagation = Propagation.REQUIRED // 默认传播行为
)
public void createOrder(Order order) {
// 创建订单业务
orderDao.save(order);
inventoryDao.deduct(order.getProductId(), order.getQuantity());
}
}
7.3 事务传播行为(面试重点)
| 传播行为 | 说明 | 适用场景 |
|---|---|---|
| REQUIRED | 有事务加入,无事务新建(默认) | 大多数业务方法 |
| REQUIRES_NEW | 新建事务,挂起当前事务 | 日志记录、异步操作 |
| NESTED | 嵌套事务 | 复杂业务流程 |
| SUPPORTS | 有事务加入,无事务非事务运行 | 查询操作 |
实战案例:独立日志事务
java
@Service
public class TransferService {
@Autowired
private LogService logService;
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
try {
// 转账业务
accountDao.transfer(from, to, amount);
} finally {
// 记录日志(独立事务,不受转账失败影响)
logService.recordTransferLog(from, to, amount);
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 🔑 新建独立事务
public void recordTransferLog(String from, String to, BigDecimal amount) {
logDao.save(new TransferLog(from, to, amount));
}
}
八、常见问题与解决方案
8.1 AOP 不生效排查
-
检查依赖:确认
aspectjweaver已引入 -
检查注解:
@EnableAspectJAutoProxy是否配置 -
检查包扫描:切面类是否在扫描路径内
-
检查代理对象:从容器获取的应该是代理对象
8.2 事务不生效排查
-
异常类型:默认只回滚 RuntimeException
-
方法可见性:protected/private 方法事务可能不生效
-
自调用问题:同类方法调用不走代理
-
异常被捕获:try-catch 捕获异常不会触发回滚
8.3 自调用问题解决方案
java
@Service
public class UserService {
public void batchUpdate(List<User> users) {
// 错误:自调用,事务不生效
// users.forEach(this::updateUser);
// 正确:通过代理对象调用
UserService proxy = (UserService) AopContext.currentProxy();
users.forEach(proxy::updateUser);
}
@Transactional
public void updateUser(User user) {
userDao.update(user);
}
}
九、高频面试题
9.1 Spring AOP 和 AspectJ 的区别?
-
Spring AOP:运行时织入,基于代理,只支持方法级别的切面
-
AspectJ:编译时/类加载时织入,功能更强大,支持字段、构造器切面
9.2 JDK 动态代理和 CGLIB 的区别?
-
JDK 动态代理:基于接口,生成实现类
-
CGLIB:基于继承,生成子类(可代理无接口的类)
9.3 @Transactional 的工作原理?
基于 AOP 实现,在方法执行前开启事务,执行后提交/回滚事务
9.4 什么情况下事务会失效?
-
方法不是 public
-
异常被 catch 没有抛出
-
同类方法自调用
-
数据库引擎不支持事务
十、最佳实践总结
10.1 AOP 使用建议
-
切入点表达式尽量精确,避免过度使用通配符
-
环绕通知要正确处理异常和返回值
-
切面逻辑要保持轻量,避免复杂业务逻辑
10.2 事务使用建议
-
事务注解加在实现类而不是接口
-
事务范围尽量小,只在需要的方法上加
-
合理设置超时时间,避免长时间锁等待
-
根据业务需求选择合适的传播行为
10.3 性能优化
java
@Aspect
@Component
public class OptimizedAspect {
// 预编译切入点,提升性能
private static final Pointcut SERVICE_POINTCUT =
Pointcuts.forMethodInType(UserService.class);
@Around("serviceMethods() && " + SERVICE_POINTCUT)
public Object optimizedAdvice(ProceedingJoinPoint pjp) throws Throwable {
// 快速执行的切面逻辑
return pjp.proceed();
}
}
记住:AOP 和事务是 Spring 的核心功能,理解原理 + 多实践 = 掌握精髓!
1302

被折叠的 条评论
为什么被折叠?



