【SSM框架 | day26 spring AOP与事务管理】


一、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 不生效排查

  1. 检查依赖:确认 aspectjweaver 已引入

  2. 检查注解@EnableAspectJAutoProxy 是否配置

  3. 检查包扫描:切面类是否在扫描路径内

  4. 检查代理对象:从容器获取的应该是代理对象

8.2 事务不生效排查

  1. 异常类型:默认只回滚 RuntimeException

  2. 方法可见性:protected/private 方法事务可能不生效

  3. 自调用问题:同类方法调用不走代理

  4. 异常被捕获: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 什么情况下事务会失效?

  1. 方法不是 public

  2. 异常被 catch 没有抛出

  3. 同类方法自调用

  4. 数据库引擎不支持事务

十、最佳实践总结

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 的核心功能,理解原理 + 多实践 = 掌握精髓!

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值