🎯 第14章 SpringBoot AOP

掌握面向切面编程,实现横切关注点的优雅处理

💻 查看完整代码 - 在线IDE体验

📋 学习目标

  • 理解AOP概念 - 面向切面编程思想
  • 掌握核心术语 - 切面、切点、通知等
  • 学会配置AOP - 注解与XML配置
  • 实现通知类型 - 前置、后置、环绕通知
  • 掌握切点表达式 - 精确定位目标方法
  • 应用实战场景 - 日志、事务、权限控制

🔍 AOP核心概念

  • 切面(Aspect) - 横切关注点的模块化
  • 连接点(JoinPoint) - 程序执行的特定点
  • 切点(Pointcut) - 连接点的集合
  • 通知(Advice) - 切面在特定连接点执行的动作
  • 目标对象(Target) - 被通知的对象
  • 代理(Proxy) - AOP框架创建的对象

📦 AOP依赖配置

<!-- pom.xml AOP依赖配置 --> <dependencies> <!-- SpringBoot AOP启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- SpringBoot Web启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot 测试启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> # application.yml AOP配置 spring: aop: # 启用AOP自动配置 auto: true # 使用CGLIB代理 proxy-target-class: true # 日志配置 logging: level: cn.bugstack.springframework.boot.aop: DEBUG org.springframework.aop: DEBUG pattern: console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"

🔄 AOP执行流程

1
目标方法调用
客户端调用目标对象的方法
2
代理拦截
AOP代理拦截方法调用
3
前置通知
执行@Before通知方法
4
目标方法执行
调用实际的目标方法
5
后置通知
执行@After、@AfterReturning等通知

🎯 切面实现示例

// LoggingAspect.java - 日志切面 @Aspect @Component @Slf4j public class LoggingAspect { /** * 定义切点:拦截service包下所有类的所有方法 */ @Pointcut("execution(* cn.bugstack.springframework.boot.aop.service.*.*(..))") public void servicePointcut() {} /** * 前置通知:方法执行前记录日志 */ @Before("servicePointcut()") public void beforeAdvice(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); Object[] args = joinPoint.getArgs(); log.info("=== 方法执行前 ==="); log.info("类名: {}", className); log.info("方法名: {}", methodName); log.info("参数: {}", Arrays.toString(args)); log.info("执行时间: {}", LocalDateTime.now()); } /** * 后置通知:方法执行后记录日志 */ @AfterReturning(pointcut = "servicePointcut()", returning = "result") public void afterReturningAdvice(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); log.info("=== 方法执行后 ==="); log.info("方法名: {}", methodName); log.info("返回值: {}", result); log.info("执行完成时间: {}", LocalDateTime.now()); } /** * 异常通知:方法抛出异常时记录日志 */ @AfterThrowing(pointcut = "servicePointcut()", throwing = "exception") public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) { String methodName = joinPoint.getSignature().getName(); log.error("=== 方法执行异常 ==="); log.error("方法名: {}", methodName); log.error("异常信息: {}", exception.getMessage()); log.error("异常类型: {}", exception.getClass().getSimpleName()); } /** * 环绕通知:完全控制方法执行 */ @Around("servicePointcut()") public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String methodName = proceedingJoinPoint.getSignature().getName(); long startTime = System.currentTimeMillis(); log.info("=== 环绕通知开始 ==="); log.info("方法名: {}", methodName); try { // 执行目标方法 Object result = proceedingJoinPoint.proceed(); long endTime = System.currentTimeMillis(); log.info("=== 环绕通知结束 ==="); log.info("方法名: {}", methodName); log.info("执行耗时: {}ms", endTime - startTime); return result; } catch (Exception e) { log.error("环绕通知捕获异常: {}", e.getMessage()); throw e; } } } // UserService.java - 目标服务类 @Service @Slf4j public class UserService { public User getUserById(Long id) { log.info("查询用户信息,ID: {}", id); // 模拟数据库查询 if (id == null || id <= 0) { throw new IllegalArgumentException("用户ID不能为空或小于等于0"); } User user = new User(); user.setId(id); user.setUsername("user" + id); user.setEmail("user" + id + "@example.com"); user.setCreateTime(LocalDateTime.now()); return user; } public List<User> getAllUsers() { log.info("查询所有用户信息"); List<User> users = new ArrayList<>(); for (int i = 1; i <= 3; i++) { User user = new User(); user.setId((long) i); user.setUsername("user" + i); user.setEmail("user" + i + "@example.com"); user.setCreateTime(LocalDateTime.now()); users.add(user); } return users; } public User saveUser(User user) { log.info("保存用户信息: {}", user); if (user == null) { throw new IllegalArgumentException("用户信息不能为空"); } // 模拟保存操作 user.setId(System.currentTimeMillis()); user.setCreateTime(LocalDateTime.now()); return user; } } // User.java - 用户实体类 @Data @NoArgsConstructor @AllArgsConstructor public class User { private Long id; private String username; private String email; private LocalDateTime createTime; }

🎨 AOP切面图示

@Before
前置通知
Target Method
目标方法
@After
后置通知

@Around 环绕通知可以完全控制整个执行流程

@AfterThrowing 异常通知在方法抛出异常时执行

@AfterReturning 返回通知在方法正常返回时执行

📍 切点表达式

  • execution - 匹配方法执行
  • within - 匹配类型内的方法
  • this - 匹配代理对象类型
  • target - 匹配目标对象类型
  • args - 匹配方法参数
  • @annotation - 匹配注解

📢 通知类型

  • @Before - 前置通知,方法执行前
  • @After - 后置通知,方法执行后
  • @AfterReturning - 返回通知,正常返回后
  • @AfterThrowing - 异常通知,抛出异常后
  • @Around - 环绕通知,完全控制执行
  • @Pointcut - 切点定义,可重用

📊 性能监控切面

// PerformanceAspect.java - 性能监控切面 @Aspect @Component @Slf4j public class PerformanceAspect { /** * 自定义注解切点 */ @Pointcut("@annotation(cn.bugstack.springframework.boot.aop.annotation.PerformanceMonitor)") public void performancePointcut() {} /** * 环绕通知:监控方法执行性能 */ @Around("performancePointcut()") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); // 记录开始时间 long startTime = System.currentTimeMillis(); StopWatch stopWatch = new StopWatch(); stopWatch.start(); log.info("🚀 开始执行方法: {}.{}", className, methodName); try { // 执行目标方法 Object result = joinPoint.proceed(); // 记录结束时间 stopWatch.stop(); long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; // 性能分析 if (executionTime > 1000) { log.warn("⚠️ 慢方法警告: {}.{} 执行耗时: {}ms", className, methodName, executionTime); } else if (executionTime > 500) { log.info("⏰ 方法执行: {}.{} 执行耗时: {}ms", className, methodName, executionTime); } else { log.debug("✅ 方法执行: {}.{} 执行耗时: {}ms", className, methodName, executionTime); } // 记录到监控系统(可集成Micrometer) recordMetrics(className, methodName, executionTime); return result; } catch (Exception e) { stopWatch.stop(); long executionTime = System.currentTimeMillis() - startTime; log.error("❌ 方法执行异常: {}.{} 执行耗时: {}ms 异常: {}", className, methodName, executionTime, e.getMessage()); // 记录异常指标 recordErrorMetrics(className, methodName, e); throw e; } } /** * 记录性能指标 */ private void recordMetrics(String className, String methodName, long executionTime) { // 可以集成Micrometer、Prometheus等监控系统 Timer.Sample sample = Timer.start(); sample.stop(Timer.builder("method.execution.time") .tag("class", className) .tag("method", methodName) .register(Metrics.globalRegistry)); } /** * 记录异常指标 */ private void recordErrorMetrics(String className, String methodName, Exception e) { Counter.builder("method.execution.error") .tag("class", className) .tag("method", methodName) .tag("exception", e.getClass().getSimpleName()) .register(Metrics.globalRegistry) .increment(); } } // PerformanceMonitor.java - 性能监控注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PerformanceMonitor { /** * 监控描述 */ String value() default ""; /** * 慢方法阈值(毫秒) */ long threshold() default 1000; } // 使用示例 @Service public class OrderService { @PerformanceMonitor("查询订单信息") public Order getOrderById(Long orderId) { // 模拟数据库查询 try { Thread.sleep(800); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return new Order(orderId, "ORDER-" + orderId, new BigDecimal("99.99")); } @PerformanceMonitor(value = "创建订单", threshold = 2000) public Order createOrder(Order order) { // 模拟复杂业务逻辑 try { Thread.sleep(1500); // 模拟耗时操作 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } order.setId(System.currentTimeMillis()); return order; } }

🔐 权限控制切面

// SecurityAspect.java - 权限控制切面 @Aspect @Component @Slf4j public class SecurityAspect { @Autowired private UserContext userContext; /** * 权限检查切点 */ @Pointcut("@annotation(cn.bugstack.springframework.boot.aop.annotation.RequirePermission)") public void securityPointcut() {} /** * 前置通知:权限检查 */ @Before("securityPointcut() && @annotation(requirePermission)") public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) { String methodName = joinPoint.getSignature().getName(); String className = joinPoint.getTarget().getClass().getSimpleName(); String[] permissions = requirePermission.value(); log.info("🔒 权限检查: {}.{} 需要权限: {}", className, methodName, Arrays.toString(permissions)); // 获取当前用户 User currentUser = userContext.getCurrentUser(); if (currentUser == null) { log.warn("❌ 权限检查失败: 用户未登录"); throw new SecurityException("用户未登录,请先登录"); } // 检查用户权限 Set<String> userPermissions = getUserPermissions(currentUser); boolean hasPermission = Arrays.stream(permissions) .anyMatch(userPermissions::contains); if (!hasPermission) { log.warn("❌ 权限检查失败: 用户 {} 缺少权限 {}", currentUser.getUsername(), Arrays.toString(permissions)); throw new SecurityException("权限不足,无法访问该资源"); } log.info("✅ 权限检查通过: 用户 {} 访问 {}.{}", currentUser.getUsername(), className, methodName); } /** * 获取用户权限 */ private Set<String> getUserPermissions(User user) { // 模拟从数据库或缓存中获取用户权限 Set<String> permissions = new HashSet<>(); if ("admin".equals(user.getUsername())) { permissions.add("user:read"); permissions.add("user:write"); permissions.add("user:delete"); permissions.add("order:read"); permissions.add("order:write"); } else if ("manager".equals(user.getUsername())) { permissions.add("user:read"); permissions.add("order:read"); permissions.add("order:write"); } else { permissions.add("user:read"); permissions.add("order:read"); } return permissions; } } // RequirePermission.java - 权限注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequirePermission { /** * 需要的权限列表 */ String[] value(); /** * 权限检查模式:ALL-需要所有权限,ANY-需要任一权限 */ PermissionMode mode() default PermissionMode.ANY; } // PermissionMode.java - 权限模式枚举 public enum PermissionMode { ALL, // 需要所有权限 ANY // 需要任一权限 } // 使用示例 @RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") @RequirePermission("user:read") public User getUserById(@PathVariable Long id) { return userService.getUserById(id); } @PostMapping @RequirePermission("user:write") public User createUser(@RequestBody User user) { return userService.saveUser(user); } @DeleteMapping("/{id}") @RequirePermission(value = {"user:delete", "admin"}, mode = PermissionMode.ANY) public void deleteUser(@PathVariable Long id) { userService.deleteUser(id); } }

📊 AOP注解对比

注解类型 执行时机 参数 使用场景
@Before 方法执行前 JoinPoint 参数校验、权限检查
@After 方法执行后(无论成功失败) JoinPoint 资源清理、日志记录
@AfterReturning 方法正常返回后 JoinPoint, 返回值 结果处理、缓存更新
@AfterThrowing 方法抛出异常后 JoinPoint, 异常对象 异常处理、错误日志
@Around 完全控制方法执行 ProceedingJoinPoint 性能监控、事务管理

💡 AOP最佳实践

🎯 设计原则

单一职责 - 每个切面只处理一种横切关注点

最小侵入 - 尽量减少对业务代码的影响

性能考虑 - 避免在高频方法上使用复杂切面

异常处理 - 切面中的异常要妥善处理

测试友好 - 确保切面逻辑可以被单独测试

// AOP配置类 @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { /** * 配置切面执行顺序 */ @Bean @Order(1) public SecurityAspect securityAspect() { return new SecurityAspect(); } @Bean @Order(2) public LoggingAspect loggingAspect() { return new LoggingAspect(); } @Bean @Order(3) public PerformanceAspect performanceAspect() { return new PerformanceAspect(); } } // 切面测试 @SpringBootTest class AopTest { @Autowired private UserService userService; @Test void testAopLogging() { // 测试AOP日志功能 User user = userService.getUserById(1L); assertNotNull(user); assertEquals("user1", user.getUsername()); } @Test void testAopException() { // 测试AOP异常处理 assertThrows(IllegalArgumentException.class, () -> { userService.getUserById(-1L); }); } }

🎉 恭喜完成第14章学习!

你已经掌握了SpringBoot AOP的核心技能,接下来学习缓存机制!

🚀 下一章:SpringBoot Cache 🏠 返回课程首页