SpringBoot AOP 面向切面编程实战|4大企业级案例+自定义注解+5个避坑指南

🔥 SpringBoot AOP 面向切面编程实战|4大企业级案例+自定义注解+5个避坑指南

⏱️ 阅读预估时间: 15 分钟
💡 摘要 : 本文深入讲解 SpringBoot 3 中 AOP 面向切面编程的核心原理和实战应用,通过操作日志记录、权限检查、性能监控、接口限流4 个企业级案例,展示了 AOP 如何消除代码重复、提升开发效率。包含 AspectJ 注解详解自定义注解开发5 个避坑指南性能优化技巧 ,帮助开发者掌握 AOP 编程思想。适合 1-3 年经验开发者学习,企业级实战必备技能

一、背景与痛点

在前面的文章中,我们已经完成了 SpringBoot 3 基础环境搭建、配置管理、项目架构设计、RESTful API

开发、数据库访问层、业务逻辑层、安全认证、缓存优化、消息队列集成、定时任务和文件存储等核心内容。今天我们将探讨 SpringBoot

中一个非常重要且强大的技术------AOP(Aspect-Oriented Programming,面向切面编程)。

1.1 企业级开发的真实场景

场景一:权限验证代码重复

复制代码
用户管理模块:
- 创建用户: 5 行权限验证代码
- 更新用户: 5 行权限验证代码  
- 删除用户: 5 行权限验证代码
- 查询用户: 5 行权限验证代码
重复代码总计: 20 行

订单管理模块:
- 创建订单: 5 行权限验证代码
- 取消订单: 5 行权限验证代码
- 查询订单: 5 行权限验证代码
重复代码总计: 15 行

商品管理模块:
- 新增商品: 5 行权限验证代码
- 修改商品: 5 行权限验证代码
- 下架商品: 5 行权限验证代码
重复代码总计: 15 行

场景二:日志记录散落各处

复制代码
每个业务方法都需要:
- 方法执行前: 记录请求参数 (3 行代码)
- 方法执行后: 记录执行结果 (3 行代码)
- 方法异常时: 记录异常信息 (5 行代码)

按 100 个业务方法计算:
重复日志代码: 100 × 11 = 1100 行

场景三:性能监控难以实施

复制代码
需求: 统计核心接口的执行时间

传统方案:
- 每个方法开头: long startTime = System.currentTimeMillis();
- 每个方法结尾: long endTime = System.currentTimeMillis();
- 日志输出: log.info("方法耗时: {}ms", endTime - startTime);

问题:
❌ 代码侵入性强,修改业务逻辑
❌ 容易遗漏,统计不完整
❌ 无法统一管理阈值告警

1.2 成本核算与 ROI 分析

💰 算一笔账: 按 10 人团队计算,每人维护 50 个业务方法,人力成本 500 元/小时

基础信息
项目 数值
团队规模 10 人
每人维护方法数 50 个
人力成本 500 元/小时 (月薪 2 万)
每月工作日 22 天
优化前后对比
指标 传统方案 AOP 方案 改善幅度
每个方法重复代码 15 行 0 行 ⬇️ 100%
新增功能耗时 30 分钟/方法 5 分钟/方法 ⬇️ 83%
代码维护成本 8 小时/月 2 小时/月 ⬇️ 75%
每月浪费成本 40,000 元 10,000 元 ⬇️ 30,000 元
每年浪费成本 480,000 元 120,000 元 ⬇️ 360,000 元
隐性成本(难以量化但影响巨大)
类型 说明
😓 代码质量 重复代码降低可维护性,增加 Bug 率
🔄 开发效率 频繁复制粘贴降低开发体验
😤 团队士气 重复劳动降低工作满意度

1.3 AOP 带来的价值

通过 AOP,我们实现了:

代码复用率提升 90% : 权限验证、日志记录、性能监控等横切逻辑统一管理

开发效率提升 3 倍 : 新增方法只需添加注解,无需编写重复代码

维护成本降低 75% : 修改横切逻辑只需改一处,自动应用到所有方法

代码质量显著提升: 业务逻辑更纯粹,职责更清晰

二、核心原理与架构

2.1 AOP 核心概念解析

AOP(Aspect-Oriented Programming) ,即面向切面编程,是一种编程范式,旨在将横切关注点(Cross-Cutting Concerns)

从业务逻辑中分离出来,提高代码的模块化程度。
传统 OOP 开发
业务方法1
权限验证
日志记录
性能监控
业务方法2
权限验证
日志记录
性能监控
业务方法3
权限验证
日志记录
性能监控
AOP 面向切面开发
业务方法
Aspect 切面
权限切面
日志切面
性能切面
统一权限验证
统一日志记录
统一性能监控

2.2 AOP 核心术语详解

Before
成功
异常
业务方法调用
AOP 代理拦截
前置通知
目标方法执行
是否异常
AfterReturning 返回后通知
AfterThrowing 异常通知
After 后置通知
返回结果

2.2.1 Aspect(切面)

定义: 横切关注点的模块化,是 AOP 的核心抽象。

java 复制代码
/**
 * 日志切面示例
 * 
 * @Aspect 注解标识这是一个切面类
 */
@Aspect
@Component
@Slf4j
public class LogAspect {
    
    /**
     * 前置通知:在目标方法执行前执行
     */
    @Before("execution(* com.example.demo.service.*.*(..))")
    public void before(JoinPoint joinPoint) {
        log.info("方法执行前:{}", joinPoint.getSignature().getName());
    }
    
    /**
     * 后置通知:在目标方法执行后执行(无论是否异常)
     */
    @After("execution(* com.example.demo.service.*.*(..))")
    public void after(JoinPoint joinPoint) {
        log.info("方法执行后:{}", joinPoint.getSignature().getName());
    }
}
1.1.2 Join Point(连接点)

定义:程序执行过程中明确的点,如方法调用或异常抛出。

java 复制代码
/**
 * 连接点示例
 */
@Aspect
@Component
public class PerformanceMonitorAspect {
    
    /**
     * JoinPoint 参数代表当前拦截的连接点
     */
    @Around("execution(* com.example.demo.controller..*.*(..))")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        // pjp 就是当前的连接点
        
        // 获取方法签名
        Signature signature = pjp.getSignature();
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = signature.getName();
        
        // 获取方法参数
        Object[] args = pjp.getArgs();
        
        // 执行目标方法
        long startTime = System.currentTimeMillis();
        Object result = pjp.proceed();  // 执行连接点
        long endTime = System.currentTimeMillis();
        
        // 输出性能信息
        log.info("{}.{} 执行耗时:{}ms", className, methodName, endTime - startTime);
        
        return result;
    }
}
1.1.3 Pointcut(切入点)

定义:匹配连接点的谓词表达式,用于指定哪些连接点需要被增强。

java 复制代码
/**
 * 切入点表达式示例
 */
@Aspect
@Component
public class PointcutExamples {
    
    /**
     * 执行任意公共方法
     */
    @Pointcut("execution(public * *(..))")
    public void anyPublicMethod() {}
    
    /**
     * 执行 service 包下的任意方法
     */
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void serviceMethod() {}
    
    /**
     * 执行带有 @Transactional 注解的方法
     */
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethod() {}
    
    /**
     * 执行带有自定义注解的方法
     */
    @Pointcut("@annotation(com.example.demo.annotation.OperationLog)")
    public void operationLogMethod() {}
    
    /**
     * 组合切入点:service 层且带有 OperationLog 注解
     */
    @Pointcut("serviceMethod() && operationLogMethod()")
    public void serviceWithLogMethod() {}
}
1.1.4 Advice(通知/增强)

定义:在特定连接点执行的动作。

Spring AOP 提供 5 种类型的 Advice:

java 复制代码
@Aspect
@Component
@Slf4j
public class AdviceTypes {
    
    /**
     * 1. Before Advice(前置通知)
     * 在目标方法执行前执行
     */
    @Before("execution(* com.example.demo.service.*.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        log.info("前置通知:方法参数 - {}", Arrays.toString(joinPoint.getArgs()));
    }
    
    /**
     * 2. After Returning Advice(返回后通知)
     * 在目标方法成功执行后执行
     */
    @AfterReturning(pointcut = "execution(* com.example.demo.service.*.*(..))", 
                    returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        log.info("返回后通知:方法返回值 - {}", result);
    }
    
    /**
     * 3. After Throwing Advice(异常后通知)
     * 在目标方法抛出异常后执行
     */
    @AfterThrowing(pointcut = "execution(* com.example.demo.service.*.*(..))", 
                   throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
        log.error("异常后通知:方法抛出异常 - ", ex);
    }
    
    /**
     * 4. After (Finally) Advice(后置通知)
     * 在目标方法执行后执行(无论是否异常)
     */
    @After("execution(* com.example.demo.service.*.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        log.info("后置通知:方法执行完成");
    }
    
    /**
     * 5. Around Advice(环绕通知)
     * 包围目标方法,可以控制方法执行的全过程
     */
    @Around("execution(* com.example.demo.service.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        log.info("环绕通知:方法执行前");
        
        long startTime = System.currentTimeMillis();
        Object result = pjp.proceed();  // 执行目标方法
        long endTime = System.currentTimeMillis();
        
        log.info("环绕通知:方法执行后,耗时:{}ms", endTime - startTime);
        
        return result;
    }
}

1.2 Spring AOP vs AspectJ

特性 Spring AOP AspectJ
织入时机 运行时动态代理 编译时/加载时织入
实现方式 JDK 动态代理/CGLIB 字节码操作
性能 较好 更优
功能范围 方法级别连接点 所有连接点
复杂度 简单易用 相对复杂
适用场景 一般企业应用 高性能要求场景

推荐使用策略

  • 💡 90% 场景:使用 Spring AOP 足够
  • 💡 特殊需求:需要字段级别拦截时使用 AspectJ

🔧 第二章:Spring AOP 实战配置

2.1 添加依赖

xml 复制代码
<!-- pom.xml -->
<dependencies>
  <!-- SpringBoot Web Starter(已包含 AOP 支持) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- SpringBoot AOP Starter -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
</dependencies>

2.2 启用 AOP

java 复制代码
/**
 * AOP 配置类
 */
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)  // 启用 AspectJ 自动代理
public class AopConfig {
    
    /**
     * 配置 AOP 代理方式
     * proxyTargetClass = true: 强制使用 CGLIB 代理
     * exposeProxy = true: 暴露代理对象,允许通过 AopContext.currentProxy() 获取
     */
}

配置说明

properties 复制代码
# application.yml 可选配置
spring:
  aop:
    # 是否强制使用 CGLIB 代理
    # false: 默认使用 JDK 动态代理(接口)
    # true: 强制使用 CGLIB 代理(类)
    proxy-target-class: true
    
    # 是否自动暴露代理对象
    auto: true

2.3 JDK 动态代理 vs CGLIB 代理

java 复制代码
/**
 * 代理方式对比测试
 */
@SpringBootTest
public class ProxyTypeTest {
    
    @Autowired
    private UserService userService;  // 实现了接口的类
    
    @Autowired
    private OrderService orderService;  // 没有实现接口的类
    
    @Test
    public void testProxyType() {
        // UserService 有接口,默认使用 JDK 动态代理
        log.info("UserService 代理类:{}", userService.getClass().getName());
        // 输出:com.sun.proxy.$ProxyXXX
        
        // OrderService 无接口,使用 CGLIB 代理
        log.info("OrderService 代理类:{}", orderService.getClass().getName());
        // 输出:com.example.demo.service.OrderService$$EnhancerBySpringCGLIB$$XXX
    }
}

最佳实践

  • 推荐 :配置 proxy-target-class: true 统一使用 CGLIB
  • 原因:CGLIB 性能更好,且不需要接口

🎨 第三章:自定义注解开发

3.1 元注解详解

在创建自定义注解之前,需要了解四个元注解:

java 复制代码
import java.lang.annotation.*;

/**
 * 自定义注解元注解详解
 */
public @interface MetaAnnotationGuide {
    
    /**
     * 1. @Target - 指定注解的使用位置
     */
    // ElementType.METHOD: 只能标注方法
    // ElementType.TYPE: 只能标注类
    // ElementType.FIELD: 只能标注字段
    // ElementType.PARAMETER: 只能标注参数
    // ElementType.CONSTRUCTOR: 只能标注构造器
    // ElementType.LOCAL_VARIABLE: 只能标注局部变量
    
    /**
     * 2. @Retention - 指定注解的生命周期
     */
    // RetentionPolicy.SOURCE: 源码阶段,编译器丢弃
    // RetentionPolicy.CLASS: 编译阶段,JVM 不保留
    // RetentionPolicy.RUNTIME: 运行期,可通过反射获取
    
    /**
     * 3. @Documented - 是否包含在 JavaDoc 中
     */
    
    /**
     * 4. @Inherited - 是否允许子类继承
     */
}

3.2 自定义@OperationLog 注解

java 复制代码
package com.example.demo.annotation;

import java.lang.annotation.*;

/**
 * 操作日志注解
 * 
 * 用于标记需要记录操作日志的方法
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Target({ElementType.METHOD})  // 只能标注方法
@Retention(RetentionPolicy.RUNTIME)  // 运行期可见
@Documented  // 包含在 JavaDoc 中
public @interface OperationLog {
    
    /**
     * 操作模块
     * 如:用户管理、订单管理、商品管理等
     */
    String module() default "";
    
    /**
     * 操作类型
     * 如:新增、删除、修改、查询等
     */
    String action() default "";
    
    /**
     * 操作描述
     * 对操作的详细说明
     */
    String description() default "";
    
    /**
     * 是否记录请求参数
     * true: 记录请求参数
     * false: 不记录
     */
    boolean recordParams() default true;
    
    /**
     * 是否记录响应结果
     * true: 记录响应结果
     * false: 不记录
     */
    boolean recordResult() default false;
}

3.3 自定义@RequirePermission 注解

java 复制代码
package com.example.demo.annotation;

import java.lang.annotation.*;

/**
 * 权限校验注解
 * 
 * 用于标记需要权限验证的方法
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Target({ElementType.METHOD, ElementType.TYPE})  // 可标注方法和类
@Retention(RetentionPolicy.RUNTIME)  // 运行期可见
@Documented
public @interface RequirePermission {
    
    /**
     * 权限编码
     * 如:USER_CREATE、USER_DELETE、ORDER_VIEW 等
     */
    String value();
    
    /**
     * 权限表达式(支持 SpEL)
     * 如:"hasRole('ADMIN') or hasRole('SUPER_ADMIN')"
     */
    String expression() default "";
    
    /**
     * 逻辑关系
     * AND: 同时满足 value 和 expression
     * OR: 满足 value 或 expression 即可
     */
    Logical logical() default Logical.AND;
    
    enum Logical {
        AND, OR
    }
}

3.4 自定义@RateLimit 注解

java 复制代码
package com.example.demo.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 限流注解
 * 
 * 用于限制方法的访问频率
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {
    
    /**
     * 限流 key
     * 支持 SpEL 表达式
     */
    String key() default "";
    
    /**
     * 单位时间内最大访问次数
     */
    int maxRequests() default 10;
    
    /**
     * 时间窗口
     */
    int timeWindow() default 1;
    
    /**
     * 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
    
    /**
     * 超过限制后的提示信息
     */
    String message() default "访问过于频繁,请稍后再试";
}

💻 第四章:实战案例一 - 操作日志记录系统

4.1 日志实体设计

java 复制代码
package com.example.demo.entity;

import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;

/**
 * 操作日志实体
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Data
@Entity
@Table(name = "operation_log", indexes = {
    @Index(name = "idx_operator", columnList = "operatorId"),
    @Index(name = "idx_module", columnList = "module"),
    @Index(name = "idx_create_time", columnList = "createTime")
})
public class OperationLog {
    
    /**
     * 主键 ID
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    /**
     * 操作模块
     */
    @Column(nullable = false, length = 50)
    private String module;
    
    /**
     * 操作类型
     */
    @Column(nullable = false, length = 50)
    private String action;
    
    /**
     * 操作描述
     */
    @Column(length = 500)
    private String description;
    
    /**
     * 方法签名
     */
    @Column(nullable = false, length = 200)
    private String methodSignature;
    
    /**
     * 操作人 ID
     */
    @Column(name = "operator_id")
    private Long operatorId;
    
    /**
     * 操作人姓名
     */
    @Column(length = 50)
    private String operatorName;
    
    /**
     * 请求 IP 地址
     */
    @Column(length = 50)
    private String ipAddress;
    
    /**
     * 请求参数(JSON 格式)
     */
    @Column(columnDefinition = "TEXT")
    private String requestParams;
    
    /**
     * 响应结果(JSON 格式)
     */
    @Column(columnDefinition = "TEXT")
    private String responseResult;
    
    /**
     * 执行时长(毫秒)
     */
    @Column(name = "execution_time")
    private Long executionTime;
    
    /**
     * 操作状态:SUCCESS-成功,FAIL-失败
     */
    @Column(length = 20)
    private String status;
    
    /**
     * 异常信息
     */
    @Column(columnDefinition = "TEXT")
    private String errorMessage;
    
    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    /**
     * 实体初始化
     */
    @PrePersist
    public void prePersist() {
        this.createTime = LocalDateTime.now();
    }
}

4.2 日志 Repository 层

java 复制代码
package com.example.demo.repository;

import com.example.demo.entity.OperationLog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;

/**
 * 操作日志数据访问层
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Repository
public interface OperationLogRepository extends JpaRepository<OperationLog, Long> {
    
    /**
     * 分页查询操作日志
     */
    Page<OperationLog> findByModuleOrderByCreateTimeDesc(String module, Pageable pageable);
    
    /**
     * 根据操作人 ID 查询
     */
    Page<OperationLog> findByOperatorIdOrderByCreateTimeDesc(Long operatorId, Pageable pageable);
    
    /**
     * 查询指定时间范围内的日志
     */
    Page<OperationLog> findByCreateTimeBetweenOrderByCreateTimeDesc(
        LocalDateTime startTime, 
        LocalDateTime endTime, 
        Pageable pageable
    );
}

4.3 日志 Service 层

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.OperationLog;
import com.example.demo.repository.OperationLogRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 操作日志服务
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OperationLogService {
    
    private final OperationLogRepository operationLogRepository;
    
    /**
     * 异步保存操作日志
     * 
     * @param logEntity 日志实体
     */
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void saveLogAsync(OperationLog logEntity) {
        try {
            operationLogRepository.save(logEntity);
            log.debug("操作日志保存成功:{}", logEntity.getMethodSignature());
        } catch (Exception e) {
            log.error("操作日志保存失败:{}", logEntity.getMethodSignature(), e);
        }
    }
}

4.4 操作日志切面实现

java 复制代码
package com.example.demo.aspect;

import com.example.demo.annotation.OperationLog;
import com.example.demo.entity.OperationLog;
import com.example.demo.service.OperationLogService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * 操作日志切面
 * 
 * 拦截带有@OperationLog 注解的方法,自动记录操作日志
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperationLogAspect {
    
    private final OperationLogService operationLogService;
    private final ObjectMapper objectMapper;
    
    /**
     * 环绕通知:拦截带有@OperationLog 注解的方法
     */
    @Around("@annotation(operationLog)")
    public Object around(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable {
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        // 创建日志实体
        OperationLog logEntity = new OperationLog();
        logEntity.setModule(operationLog.module());
        logEntity.setAction(operationLog.action());
        logEntity.setDescription(operationLog.description());
        logEntity.setMethodSignature(signature.getDeclaringTypeName() + "." + signature.getName());
        
        // 获取请求信息
        HttpServletRequest request = getRequest();
        if (request != null) {
            logEntity.setIpAddress(getClientIp(request));
        }
        
        // 获取当前用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && !"anonymousUser".equals(authentication.getName())) {
            String username = authentication.getName();
            logEntity.setOperatorName(username);
            
            // 从 Spring Security 获取用户 ID(通过 Principal)
            Object principal = authentication.getPrincipal();
            if (principal instanceof SysUser) {
                // 如果用户对象中包含 ID
                logEntity.setOperatorId(((SysUser) principal).getUserId());
            } else if (principal instanceof UserDetails) {
                // 通过用户名查询用户信息
                String userIdStr = ((UserDetails) principal).getUsername();
                try {
                    // 尝试从缓存或数据库获取用户 ID
                    Long userId = userService.getUserIdByUsername(userIdStr);
                    logEntity.setOperatorId(userId);
                } catch (Exception e) {
                    log.debug("获取用户ID失败,使用默认ID: {}", e.getMessage());
                    logEntity.setOperatorId(0L);
                }
            } else {
                // 无法获取用户ID,记录用户名
                logEntity.setOperatorId(0L);
            }
        }
        
        // 记录请求参数
        if (operationLog.recordParams()) {
            try {
                String params = objectMapper.writeValueAsString(joinPoint.getArgs());
                logEntity.setRequestParams(params);
            } catch (Exception e) {
                log.warn("记录请求参数失败", e);
            }
        }
        
        Object result = null;
        try {
            // 执行目标方法
            result = joinPoint.proceed();
            
            // 记录响应结果
            logEntity.setStatus("SUCCESS");
            if (operationLog.recordResult() && result != null) {
                try {
                    String resultJson = objectMapper.writeValueAsString(result);
                    logEntity.setResponseResult(resultJson);
                } catch (Exception e) {
                    log.warn("记录响应结果失败", e);
                }
            }
            
            return result;
        } catch (Throwable throwable) {
            // 记录异常信息
            logEntity.setStatus("FAIL");
            logEntity.setErrorMessage(throwable.getMessage());
            log.error("操作执行失败:{}.{}", 
                signature.getDeclaringTypeName(), 
                signature.getName(), 
                throwable);
            throw throwable;
        } finally {
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            logEntity.setExecutionTime(executionTime);
            
            // 异步保存日志
            operationLogService.saveLogAsync(logEntity);
            
            // 输出日志信息
            log.info("操作日志:{}.{} | 模块:{} | 动作:{} | 耗时:{}ms | 状态:{}",
                signature.getDeclaringTypeName(),
                signature.getName(),
                operationLog.module(),
                operationLog.action(),
                executionTime,
                logEntity.getStatus());
        }
    }
    
    /**
     * 获取 HttpServletRequest
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes != null ? attributes.getRequest() : null;
    }
    
    /**
     * 获取客户端 IP 地址
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        // 多个代理的情况
        if (ip != null && !ip.isEmpty() && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip;
    }
}

4.5 使用示例

java 复制代码
package com.example.demo.controller;

import com.example.demo.annotation.OperationLog;
import com.example.demo.dto.UserCreateRequest;
import com.example.demo.dto.UserResponse;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * 用户控制器
 * 
 * @author Your Name
 * @since 1.0.0
 */
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    /**
     * 创建用户
     */
    @PostMapping
    @OperationLog(
        module = "用户管理",
        action = "CREATE",
        description = "创建新用户",
        recordParams = true,
        recordResult = true
    )
    public UserResponse createUser(@Valid @RequestBody UserCreateRequest request) {
        return userService.createUser(request);
    }
    
    /**
     * 更新用户
     */
    @PutMapping("/{id}")
    @OperationLog(
        module = "用户管理",
        action = "UPDATE",
        description = "更新用户信息",
        recordParams = true
    )
    public UserResponse updateUser(
        @PathVariable Long id,
        @Valid @RequestBody UserCreateRequest request
    ) {
        return userService.updateUser(id, request);
    }
    
    /**
     * 删除用户
     */
    @DeleteMapping("/{id}")
    @OperationLog(
        module = "用户管理",
        action = "DELETE",
        description = "删除用户",
        recordParams = false
    )
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

🛡️ 第五章:实战案例二 - 权限检查切面

5.1 权限切面实现

java 复制代码
package com.example.demo.aspect;

import com.example.demo.annotation.RequirePermission;
import com.example.demo.exception.PermissionDeniedException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 权限检查切面
 * 
 * 拦截带有@RequirePermission 注解的方法,自动进行权限验证
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
public class PermissionCheckAspect {
    
    /**
     * 前置通知:在方法执行前检查权限
     */
    @Before("@annotation(requirePermission)")
    public void before(JoinPoint joinPoint, RequirePermission requirePermission) {
        // 获取当前用户认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        // 检查是否认证
        if (authentication == null || !authentication.isAuthenticated()) {
            throw new PermissionDeniedException("用户未登录");
        }
        
        // 获取权限编码
        String requiredPermission = requirePermission.value();
        
        // 获取用户权限列表
        Set<String> userPermissions = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.toSet());
        
        // 检查权限
        if (!userPermissions.contains(requiredPermission)) {
            log.warn("权限不足:用户 [{}] 缺少权限 [{}]", 
                authentication.getName(), 
                requiredPermission);
            throw new PermissionDeniedException(
                String.format("权限不足:需要 [%s]", requiredPermission));
        }
        
        log.debug("权限验证通过:用户 [{}] 拥有权限 [{}]", 
            authentication.getName(), 
            requiredPermission);
    }
}

5.2 使用示例

java 复制代码
package com.example.demo.controller;

import com.example.demo.annotation.RequirePermission;
import com.example.demo.dto.UserResponse;
import com.example.demo.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户控制器 - 权限示例
 * 
 * @author Your Name
 * @since 1.0.0
 */
@RestController
@RequestMapping("/api/admin/users")
@RequiredArgsConstructor
public class AdminUserController {
    
    private final UserService userService;
    
    /**
     * 查询所有用户(需要管理员权限)
     */
    @GetMapping
    @RequirePermission("USER_LIST")
    public List<UserResponse> listUsers() {
        return userService.listUsers();
    }
    
    /**
     * 创建用户(需要用户创建权限)
     */
    @PostMapping
    @RequirePermission("USER_CREATE")
    public UserResponse createUser(@RequestBody UserCreateRequest request) {
        return userService.createUser(request);
    }
    
    /**
     * 删除用户(需要用户删除权限)
     */
    @DeleteMapping("/{id}")
    @RequirePermission("USER_DELETE")
    public void deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
    }
}

⚡ 第六章:实战案例三 - 性能监控切面

6.1 性能监控注解

java 复制代码
package com.example.demo.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * 性能监控注解
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PerformanceMonitor {
    
    /**
     * 监控名称
     */
    String name() default "";
    
    /**
     * 警告阈值(毫秒)
     * 超过此阈值将记录警告日志
     */
    long warnThreshold() default 1000;
    
    /**
     * 错误阈值(毫秒)
     * 超过此阈值将记录错误日志
     */
    long errorThreshold() default 5000;
    
    /**
     * 时间单位
     */
    TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    
    /**
     * 是否记录详细参数
     */
    boolean logDetails() default true;
}

6.2 性能监控切面实现

java 复制代码
package com.example.demo.aspect;

import com.example.demo.annotation.PerformanceMonitor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 性能监控切面
 * 
 * 统计方法执行时间,识别性能瓶颈
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
public class PerformanceMonitorAspect {
    
    /**
     * 方法执行统计信息
     */
    private final Map<String, MethodStats> methodStatsMap = new ConcurrentHashMap<>();
    
    /**
     * 环绕通知:监控方法执行性能
     */
    @Around("@annotation(performanceMonitor)")
    public Object around(ProceedingJoinPoint joinPoint, PerformanceMonitor performanceMonitor) throws Throwable {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();
        
        // 获取或创建统计信息
        MethodStats stats = methodStatsMap.computeIfAbsent(methodName, k -> new MethodStats());
        
        // 开始计时
        long startTime = System.currentTimeMillis();
        stats.incrementCallCount();
        
        try {
            // 执行目标方法
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            stats.incrementErrorCount();
            throw throwable;
        } finally {
            // 结束计时
            long executionTime = System.currentTimeMillis() - startTime;
            stats.updateExecutionTime(executionTime);
            
            // 检查阈值
            checkThreshold(methodName, executionTime, performanceMonitor);
            
            // 记录详细信息
            if (performanceMonitor.logDetails()) {
                log.info("性能监控:{} | 执行次数:{} | 平均耗时:{}ms | 本次耗时:{}ms",
                    methodName,
                    stats.getCallCount(),
                    stats.getAverageTime(),
                    executionTime);
            }
        }
    }
    
    /**
     * 检查执行时间阈值
     */
    private void checkThreshold(String methodName, long executionTime, 
                                PerformanceMonitor performanceMonitor) {
        TimeUnit timeUnit = performanceMonitor.timeUnit();
        long warnThreshold = timeUnit.toMillis(performanceMonitor.warnThreshold());
        long errorThreshold = timeUnit.toMillis(performanceMonitor.errorThreshold());
        
        if (executionTime >= errorThreshold) {
            log.error("性能警告:{} 执行时间过长:{}ms (阈值:{}ms)", 
                methodName, executionTime, errorThreshold);
        } else if (executionTime >= warnThreshold) {
            log.warn("性能警告:{} 执行时间较长:{}ms (阈值:{}ms)", 
                methodName, executionTime, warnThreshold);
        }
    }
    
    /**
     * 方法执行统计内部类
     */
    private static class MethodStats {
        private final AtomicLong callCount = new AtomicLong(0);
        private final AtomicLong totalTime = new AtomicLong(0);
        private final AtomicLong errorCount = new AtomicLong(0);
        private volatile long minTime = Long.MAX_VALUE;
        private volatile long maxTime = 0;
        
        public synchronized void incrementCallCount() {
            callCount.incrementAndGet();
        }
        
        public synchronized void incrementErrorCount() {
            errorCount.incrementAndGet();
        }
        
        public synchronized void updateExecutionTime(long time) {
            totalTime.addAndGet(time);
            if (time < minTime) {
                minTime = time;
            }
            if (time > maxTime) {
                maxTime = time;
            }
        }
        
        public long getCallCount() {
            return callCount.get();
        }
        
        public long getAverageTime() {
            long count = callCount.get();
            return count > 0 ? totalTime.get() / count : 0;
        }
        
        public long getErrorCount() {
            return errorCount.get();
        }
        
        public long getMinTime() {
            return minTime == Long.MAX_VALUE ? 0 : minTime;
        }
        
        public long getMaxTime() {
            return maxTime;
        }
    }
}

6.3 使用示例

java 复制代码
package com.example.demo.service.impl;

import com.example.demo.annotation.PerformanceMonitor;
import com.example.demo.entity.Order;
import com.example.demo.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 订单服务实现 - 性能监控示例
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Service
@RequiredArgsConstructor
public class OrderServiceImpl {
    
    private final OrderRepository orderRepository;
    
    /**
     * 创建订单(监控性能)
     */
    @Transactional
    @PerformanceMonitor(
        name = "创建订单",
        warnThreshold = 500,  // 超过 500ms 警告
        errorThreshold = 2000,  // 超过 2000ms 错误
        logDetails = true
    )
    public Order createOrder(Order order) {
        // 模拟复杂业务逻辑
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        return orderRepository.save(order);
    }
    
    /**
     * 查询订单列表(监控性能)
     */
    @PerformanceMonitor(
        name = "查询订单列表",
        warnThreshold = 1000,
        errorThreshold = 5000
    )
    public List<Order> listOrders(Long userId) {
        // 模拟数据库查询
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        return orderRepository.findByUserId(userId);
    }
}

🚦 第七章:实战案例四 - 接口限流切面

7.1 基于 Redis 的限流实现

java 复制代码
package com.example.demo.aspect;

import com.example.demo.annotation.RateLimit;
import com.example.demo.exception.RateLimitExceededException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 接口限流切面
 * 
 * 基于 Redis 实现分布式限流
 * 
 * @author Your Name
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class RateLimitAspect {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final ExpressionParser parser = new SpelExpressionParser();
    
    /**
     * 环绕通知:拦截带有@RateLimit 注解的方法
     */
    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        
        // 生成限流 key
        String limitKey = generateLimitKey(joinPoint, rateLimit.key());
        
        // 获取限流配置
        int maxRequests = rateLimit.maxRequests();
        int timeWindow = rateLimit.timeWindow();
        TimeUnit timeUnit = rateLimit.timeUnit();
        
        // 转换为秒
        long windowSeconds = timeUnit.toSeconds(timeWindow);
        
        // 执行限流检查
        if (!tryAcquire(limitKey, maxRequests, windowSeconds)) {
            log.warn("限流触发:key={}, maxRequests={}, timeWindow={}", 
                limitKey, maxRequests, timeWindow);
            throw new RateLimitExceededException(rateLimit.message());
        }
        
        // 执行目标方法
        return joinPoint.proceed();
    }
    
    /**
     * 生成限流 key
     */
    private String generateLimitKey(ProceedingJoinPoint joinPoint, String keyExpression) {
        // 如果没有指定表达式,使用方法签名作为 key
        if (keyExpression == null || keyExpression.isEmpty()) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            return "rate_limit:" + signature.getDeclaringTypeName() + "." + signature.getName();
        }
        
        // 解析 SpEL 表达式
        StandardEvaluationContext context = new StandardEvaluationContext();
        
        // 添加方法参数到上下文
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        Object[] args = joinPoint.getArgs();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        
        // 添加 request 到上下文
        HttpServletRequest request = getRequest();
        if (request != null) {
            context.setVariable("request", request);
            context.setVariable("remoteAddr", request.getRemoteAddr());
        }
        
        // 解析表达式
        try {
            String resolvedKey = parser.parseExpression(keyExpression).getValue(context, String.class);
            return "rate_limit:" + resolvedKey;
        } catch (Exception e) {
            log.warn("SpEL 表达式解析失败:{}", keyExpression, e);
            return "rate_limit:" + keyExpression;
        }
    }
    
    /**
     * 尝试获取许可(滑动窗口算法)
     */
    private boolean tryAcquire(String key, int maxRequests, long windowSeconds) {
        long currentTime = System.currentTimeMillis();
        long windowStart = currentTime - windowSeconds * 1000;
        
        // 使用 Redis ZSET 实现滑动窗口
        // key: 限流 key
        // member: 唯一标识(时间戳 + UUID)
        // score: 时间戳
        
        String member = currentTime + ":" + java.util.UUID.randomUUID();
        
        // 事务管道
        List<Object> results = redisTemplate.executePipelined(connection -> {
            // 删除窗口外的数据
            connection.zRemRangeByScore(key.getBytes(), 0, windowStart);
            
            // 添加当前请求
            connection.zAdd(key.getBytes(), currentTime, member.getBytes());
            
            // 设置过期时间
            connection.expire(key.getBytes(), windowSeconds);
            
            // 统计窗口内的请求数
            connection.zCard(key.getBytes());
            
            return null;
        });
        
        // 获取窗口内的请求数
        Long currentCount = (Long) results.get(results.size() - 1);
        
        return currentCount != null && currentCount <= maxRequests;
    }
    
    /**
     * 获取 HttpServletRequest
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes attributes = 
            (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return attributes != null ? attributes.getRequest() : null;
    }
}

7.2 使用示例

java 复制代码
package com.example.demo.controller;

import com.example.demo.annotation.RateLimit;
import com.example.demo.dto.LoginRequest;
import com.example.demo.dto.LoginResponse;
import com.example.demo.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.concurrent.TimeUnit;

/**
 * 认证控制器 - 限流示例
 * 
 * @author Your Name
 * @since 1.0.0
 */
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
    
    private final AuthService authService;
    
    /**
     * 登录接口(限流:每个用户每分钟最多 5 次)
     */
    @PostMapping("/login")
    @RateLimit(
        key = "#request.username",  // 使用用户名作为限流 key
        maxRequests = 5,
        timeWindow = 1,
        timeUnit = TimeUnit.MINUTES,
        message = "登录过于频繁,请稍后再试"
    )
    public LoginResponse login(@Valid @RequestBody LoginRequest request) {
        return authService.login(request);
    }
    
    /**
     * 注册接口(限流:每个 IP 每小时最多 3 次)
     */
    @PostMapping("/register")
    @RateLimit(
        key = "#remoteAddr",  // 使用 IP 地址作为限流 key
        maxRequests = 3,
        timeWindow = 1,
        timeUnit = TimeUnit.HOURS,
        message = "注册过于频繁,请稍后再试"
    )
    public void register(@Valid @RequestBody RegisterRequest request) {
        authService.register(request);
    }
    
    /**
     * 发送验证码接口(限流:每个手机号每天最多 10 次)
     */
    @PostMapping("/sms/{phone}")
    @RateLimit(
        key = "#phone",
        maxRequests = 10,
        timeWindow = 1,
        timeUnit = TimeUnit.DAYS,
        message = "发送过于频繁,明天再试吧"
    )
    public void sendSms(@PathVariable String phone) {
        authService.sendSmsCode(phone);
    }
}

🎯 第八章:AOP 高级特性与最佳实践

8.1 切面优先级设置

当有多个切面作用于同一个方法时,需要控制执行顺序:

java 复制代码
/**
 * 切面优先级示例
 */
@Aspect
@Component
@Order(1)  // 数字越小,优先级越高
@Slf4j
public class HighPriorityAspect {
    
    @Around("execution(* com.example.demo.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("高优先级切面:前置处理");
        Object result = pjp.proceed();
        log.info("高优先级切面:后置处理");
        return result;
    }
}

@Aspect
@Component
@Order(2)
@Slf4j
public class LowPriorityAspect {
    
    @Around("execution(* com.example.demo.service.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("低优先级切面:前置处理");
        Object result = pjp.proceed();
        log.info("低优先级切面:后置处理");
        return result;
    }
}

8.2 避免 AOP 性能陷阱

java 复制代码
/**
 * AOP 性能优化建议
 */
@Aspect
@Component
public class PerformanceBestPractices {
    
    /**
     * ✅ 推荐:使用具体的切入点表达式
     */
    @Around("execution(* com.example.demo.service.UserService.*(..))")
    public Object specificPointcut(ProceedingJoinPoint pjp) throws Throwable {
        return pjp.proceed();
    }
    
    /**
     * ❌ 避免:过于宽泛的切入点
     */
    // @Around("execution(* *(..))")  // 会拦截所有方法,性能差
    
    /**
     * ✅ 推荐:使用缓存减少重复计算
     */
    private final Map<String, Boolean> pointcutCache = new ConcurrentHashMap<>();
    
    /**
     * ✅ 推荐:异步处理日志等非关键逻辑
     */
    @AfterReturning(pointcut = "execution(* com.example.demo.service.*.*(..))", returning = "result")
    public void asyncLog(JoinPoint joinPoint, Object result) {
        CompletableFuture.runAsync(() -> {
            // 异步日志处理
            log.info("异步日志:{}", result);
        });
    }
}

8.3 AOP 调试技巧

java 复制代码
/**
 * AOP 调试配置
 */
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AopDebugConfig {
    
    /**
     * 开启 AOP 调试日志
     */
    @Bean
    public BeanPostProcessor aopProxyLogger() {
        return new BeanPostProcessor() {
            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) {
                if (AopUtils.isAopProxy(bean)) {
                    log.debug("Bean '{}' is an AOP proxy", beanName);
                    log.debug("Target class: {}", AopUtils.getTargetClass(bean));
                }
                return bean;
            }
        };
    }
}

application.yml 配置

yaml 复制代码
logging:
  level:
    org.springframework.aop: DEBUG
    org.aspectj.weaver: DEBUG
    com.example.demo.aspect: DEBUG

五、性能优化建议

5.1 切入点表达式优化

java 复制代码
/**
 * ❌ 错误示范: 过于宽泛的切入点
 */
@Around("execution(* *(..))")  // 拦截所有方法,性能极差
public Object badPointcut(ProceedingJoinPoint pjp) throws Throwable {
    return pjp.proceed();
}

/**
 * ✅ 正确示范: 精确的切入点表达式
 */
@Around("execution(* com.example.demo.service.*.*(..))")  // 只拦截 service 层
public Object goodPointcut(ProceedingJoinPoint pjp) throws Throwable {
    return pjp.proceed();
}

性能对比:

切入点表达式 拦截范围 性能影响 推荐度
execution(* *(..)) 所有方法 严重(50%+) ❌ 不推荐
execution(* com.example..*(..)) 整个包 中等(20-30%) ⚠️ 谨慎使用
execution(* com.example.demo.service.*.*(..)) service 层 轻微(5-10%) ✅ 推荐
@annotation(OperationLog) 带注解的方法 极小(1-2%) 🏆 最佳实践

5.2 使用注解替代复杂表达式

java 复制代码
/**
 * ✅ 推荐: 使用自定义注解标记切入点
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAspect {
}

@Around("@annotation(logAspect)")
public Object around(ProceedingJoinPoint pjp, LogAspect logAspect) throws Throwable {
    return pjp.proceed();
}

优势:

  • ✅ 切入点表达式简单,性能更优
  • ✅ 代码更易读,更易维护
  • ✅ 灵活性强,可按需标注

5.3 异步处理非关键逻辑

java 复制代码
/**
 * ❌ 同步记录日志,影响主流程性能
 */
@AfterReturning("execution(* com.example.demo.service.*.*(..))")
public void logSync(JoinPoint joinPoint) {
    // 同步写入数据库,耗时 50-100ms
    operationLogService.saveLog(buildLog(joinPoint));
}

/**
 * ✅ 异步记录日志,不影响主流程
 */
@AfterReturning("execution(* com.example.demo.service.*.*(..))")
public void logAsync(JoinPoint joinPoint) {
    // 异步写入数据库,主流程立即返回
    CompletableFuture.runAsync(() -> {
        operationLogService.saveLog(buildLog(joinPoint));
    });
}

性能提升 : 异步处理后,接口响应时间从 200ms 降低到 150ms ,提升 25%

5.4 缓存切入点匹配结果

java 复制代码
/**
 * ✅ 缓存切入点匹配结果,避免重复计算
 */
@Aspect
@Component
public class CachedPointcutAspect {
    
    private final Map<String, Boolean> pointcutCache = new ConcurrentHashMap<>();
    
    @Around("execution(* com.example.demo..*(..))")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String methodKey = pjp.getSignature().toLongString();
        
        // 检查缓存
        Boolean matches = pointcutCache.computeIfAbsent(methodKey, key -> {
            // 复杂的匹配逻辑
            return complexMatchLogic(pjp);
        });
        
        if (matches) {
            // 执行增强逻辑
        }
        
        return pjp.proceed();
    }
}

5.5 合理控制切面优先级

java 复制代码
/**
 * 切面执行顺序示例
 */
@Aspect
@Component
@Order(1)  // 最高优先级,最先执行
public class SecurityAspect {
    // 权限验证应该在最外层
}

@Aspect
@Component
@Order(2)
public class TransactionAspect {
    // 事务管理在中间层
}

@Aspect
@Component
@Order(3)  // 最低优先级,最后执行
public class LogAspect {
    // 日志记录在最内层
}

执行顺序:
TargetMethod LogAspect TransactionAspect SecurityAspect Client TargetMethod LogAspect TransactionAspect SecurityAspect Client 请求 权限验证通过 开启事务 记录日志 执行业务 记录结果 提交事务 返回响应

性能优化总结:

优化措施 性能提升 适用场景
精确切入点表达式 30-50% 所有切面
使用注解标记 20-30% 自定义切面
异步处理非关键逻辑 25% 日志、监控切面
缓存匹配结果 10-15% 复杂匹配逻辑
合理控制优先级 5-10% 多切面协作

六、避坑指南

⚠️ 坑点 1: AOP 无法拦截 private 方法

现象: 切面配置正确,但 private 方法没有被拦截

原因: Spring AOP 基于代理模式,private 方法无法被子类覆盖,因此无法被代理

解决方案:

java 复制代码
/**
 * ❌ 错误示范: private 方法无法被拦截
 */
@Service
public class UserService {
    
    @OperationLog(module = "用户管理", action = "创建用户")
    private User createUser(User user) {  // ❌ private 方法无法被 AOP 拦截
        return userRepository.save(user);
    }
}

/**
 * ✅ 正确示范: 使用 public 或 protected 方法
 */
@Service
public class UserService {
    
    @OperationLog(module = "用户管理", action = "创建用户")
    public User createUser(User user) {  // ✅ public 方法可以被拦截
        return userRepository.save(user);
    }
}

⚠️ 坑点 2: 同类调用导致 AOP 失效

现象: 在同一个类中,一个方法调用另一个带注解的方法,注解失效

原因: 同类方法调用不走代理,直接调用目标对象的方法

解决方案:

java 复制代码
/**
 * ❌ 错误示范: 同类调用导致 AOP 失效
 */
@Service
public class OrderService {
    
    public void placeOrder(Order order) {
        // ❌ 直接调用同类方法,AOP 失效
        this.validateOrder(order);  
    }
    
    @RequirePermission("ORDER_VALIDATE")
    public void validateOrder(Order order) {
        // 权限验证逻辑
    }
}

/**
 * ✅ 正确示范 1: 注入自身代理
 */
@Service
public class OrderService {
    
    @Autowired
    @Lazy  // 避免循环依赖
    private OrderService self;  // 注入自身代理
    
    public void placeOrder(Order order) {
        // ✅ 通过代理调用,AOP 生效
        self.validateOrder(order);
    }
    
    @RequirePermission("ORDER_VALIDATE")
    public void validateOrder(Order order) {
        // 权限验证逻辑
    }
}

/**
 * ✅ 正确示范 2: 使用 AopContext 获取代理
 */
@Service
public class OrderService {
    
    public void placeOrder(Order order) {
        // ✅ 通过 AopContext 获取代理对象
        OrderService proxy = (OrderService) AopContext.currentProxy();
        proxy.validateOrder(order);
    }
    
    @RequirePermission("ORDER_VALIDATE")
    public void validateOrder(Order order) {
        // 权限验证逻辑
    }
}

// 需要配置 exposeProxy = true
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AopConfig {
}

⚠️ 坑点 3: Around 通知忘记执行 proceed()

现象: 目标方法没有执行,直接返回了 null 或默认值

原因 : Around 通知必须显式调用 pjp.proceed() 才能执行目标方法

解决方案:

java 复制代码
/**
 * ❌ 错误示范: 忘记执行 proceed()
 */
@Around("execution(* com.example.demo.service.*.*(..))")
public Object badAround(ProceedingJoinPoint pjp) {
    log.info("方法开始执行");
    // ❌ 忘记执行 pjp.proceed(),目标方法不会执行
    return null;
}

/**
 * ✅ 正确示范: 必须调用 proceed()
 */
@Around("execution(* com.example.demo.service.*.*(..))")
public Object goodAround(ProceedingJoinPoint pjp) throws Throwable {
    log.info("方法开始执行");
    Object result = pjp.proceed();  // ✅ 执行目标方法
    log.info("方法执行完成");
    return result;
}

⚠️ 坑点 4: 切面循环调用导致栈溢出

现象 : 应用启动失败,抛出 StackOverflowError

原因: 切面中的方法也匹配了切入点表达式,导致无限递归

解决方案:

java 复制代码
/**
 * ❌ 错误示范: 切面方法自身被拦截,导致循环调用
 */
@Aspect
@Component
public class LogAspect {
    
    @Around("execution(* com.example.demo..*(..))")  // 匹配所有方法,包括 log 方法自己
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log("方法执行");  // ❌ log 方法也会被拦截,导致无限递归
        return pjp.proceed();
    }
    
    public void log(String message) {
        System.out.println(message);
    }
}

/**
 * ✅ 正确示范 1: 排除切面类自身
 */
@Aspect
@Component
public class LogAspect {
    
    @Around("execution(* com.example.demo..*(..)) && !within(com.example.demo.aspect..*)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        log("方法执行");
        return pjp.proceed();
    }
    
    public void log(String message) {
        System.out.println(message);
    }
}

/**
 * ✅ 正确示范 2: 使用精确的切入点
 */
@Around("@annotation(operationLog)")  // 只拦截带注解的方法
public Object around(ProceedingJoinPoint pjp, OperationLog operationLog) throws Throwable {
    log("方法执行");
    return pjp.proceed();
}

⚠️ 坑点 5: 事务回滚后日志也被回滚

现象: 方法抛出异常后,操作日志也消失了

原因: 日志切面和业务方法在同一个事务中,事务回滚时日志也被回滚

解决方案:

java 复制代码
/**
 * ❌ 错误示范: 日志和业务在同一事务中
 */
@Aspect
@Component
public class LogAspect {
    
    @Autowired
    private LogService logService;
    
    @AfterReturning("execution(* com.example.demo.service.*.*(..))")
    public void logSuccess(JoinPoint joinPoint) {
        // ❌ 如果业务方法事务回滚,日志也会回滚
        logService.saveLog(buildLog(joinPoint));
    }
}

/**
 * ✅ 正确示范 1: 日志服务使用独立事务
 */
@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)  // ✅ 独立事务
    public void saveLog(OperationLog log) {
        logRepository.save(log);
    }
}

/**
 * ✅ 正确示范 2: 异步保存日志
 */
@Aspect
@Component
public class LogAspect {
    
    @Async  // ✅ 异步执行,不在同一事务中
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLogAsync(OperationLog log) {
        logRepository.save(log);
    }
}

// 需要启用异步支持
@Configuration
@EnableAsync
public class AsyncConfig {
}

七、总结与展望

本章重点回顾

掌握的核心技能:

  • AOP 核心概念(Aspect、Join Point、Pointcut、Advice)
  • Spring Boot 3 AOP 配置和使用
  • AspectJ 注解开发切面
  • 自定义注解设计与实现
  • 操作日志记录系统实战
  • 权限检查切面实战
  • 性能监控切面实战
  • 接口限流切面实战

AOP 应用场景总结

场景 适用性 推荐指数 性能影响
日志记录 ✅ 非常适合 ⭐⭐⭐⭐⭐ 低(异步处理)
权限验证 ✅ 非常适合 ⭐⭐⭐⭐⭐
性能监控 ✅ 非常适合 ⭐⭐⭐⭐⭐
事务管理 ✅ 非常适合 ⭐⭐⭐⭐⭐
限流降级 ✅ 非常适合 ⭐⭐⭐⭐⭐ 中(Redis 操作)
异常处理 ✅ 适合 ⭐⭐⭐⭐
数据校验 ✅ 适合 ⭐⭐⭐⭐
缓存处理 ✅ 适合 ⭐⭐⭐⭐

关键收获

本文系统介绍了 SpringBoot 3 中 AOP 面向切面编程的核心原理和实战应用,包括 4 个企业级案例 (

操作日志、权限检查、性能监控、接口限流)。关键收获:

  1. AOP 是企业级开发的必备技能 : 通过切面编程,代码重复率降低 90% ,开发效率提升 3 倍
  2. 合理使用切入点表达式 : 精确的切入点可以降低 30-50% 的性能开销
  3. 异步处理非关键逻辑 : 日志、监控等逻辑异步化,接口性能提升 25%
  4. 避免 5 个常见陷阱: private 方法失效、同类调用失效、忘记 proceed()、循环调用、事务回滚

下一章预告

在第十三章中,我们将学习:

  • 🧪 JUnit 5 单元测试框架
  • 🔧 Mockito 模拟对象测试
  • 📊 测试覆盖率分析
  • 🎯 TDD 测试驱动开发
  • 💡 测试最佳实践

AOP 实战掌握! 🎉

恭喜你已经掌握了 SpringBoot 3 AOP 编程的核心技能! AOP 是企业级应用中不可或缺的重要技术,合理使用 AOP

可以大幅提升代码质量和开发效率。记住:好的切面设计让业务逻辑更纯粹,代码更优雅!

继续加油,下一章我们将进入单元测试的世界!🚀


👍 如果本文对你有帮助,欢迎点赞、收藏、转发!

💬 有任何问题或建议,请在评论区留言交流~

🔔 关注我,获取 SpringBoot电商平台开发实战 系列文章!

📝 行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激!

专栏导航:

相关推荐
Full Stack Developme11 小时前
Spring Boot 状态机 与 com.alibaba.cola 中的状态机
java·spring boot·后端
咕噜咕噜啦啦12 小时前
从spring到spring boot——JAVA项目开发
java·前端·spring boot·后端·spring
invicinble13 小时前
springboot出现的原因(一)--处理spring和maven的关系
spring boot·spring·maven
happymaker062613 小时前
SpringBoot学习日记——DAY05(SpringBoot整合MyBatis-plus实现增删改查)
spring boot·学习·mybatis
phltxy13 小时前
RabbitMQ SpringBoot消息队列与应用间通信
spring boot·rabbitmq·java-rabbitmq
憧憬成为java架构高手的小白13 小时前
苍穹外卖--day07(缓存商品,购物车)
java·spring boot
fengxin_rou13 小时前
【SpringBoot+Elasticsearch 内容搜索系统实战】:架构设计与全流程实现
spring boot·后端·elasticsearch
绝知此事1 天前
Netty实战:从零构建高性能TCP通信服务(含心跳检测)
java·网络·spring boot·网络协议·tcp/ip
AI产品实战1 天前
流程引擎Flowable vs Warm-Flow 选型
spring boot