面向切面编程AOPAspect-Oriented Programming是 Spring 框架的核心特性之一它通过“横切”思想将日志、权限校验、事务管理等通用功能与业务逻辑解耦大幅提升代码复用性和可维护性。本文将从「实例演示→实现方式→核心细节→避坑注意事项」一步步带你掌握 AOP内容适合新手入门也可作为实战参考直接用于项目开发。一、先搞懂AOP 核心概念新手必看在写代码前先明确 AOP 的核心术语避免后续 confusion用通俗的话解释不堆砌专业名词切面Aspect就是我们要实现的通用功能比如日志、权限是一个独立的类包含通知和切入点。通知Advice切面的具体逻辑比如日志要打印什么内容分 5 种类型后文实例会逐个演示。切入点Pointcut指定切面作用在哪些方法上比如所有 controller 方法、带特定注解的方法。连接点JoinPoint所有可能被切面拦截的方法比如项目中所有的接口方法切入点是连接点的子集。目标对象Target被切面拦截的对象比如我们的业务ServiceImpl。一句话总结用切面Aspect定义通用功能通知并指定作用在哪些方法切入点上实现与业务逻辑的解耦。二、实战实例用 AOP 实现「接口日志记录」我们以最常用的「接口请求日志记录」为实例实现以下功能1. 记录请求的 URL、请求方法GET/POST、请求参数、请求者 IP2. 记录接口执行耗时3. 记录接口返回结果成功/失败4. 接口抛出异常时记录异常信息。技术栈Spring Boot 2.7.x Spring AOPSpring 自带无需额外引入依赖步骤 1创建 Spring Boot 项目引入依赖创建 Spring Boot 项目后无需额外引入 AOP 依赖Spring Boot 已自动集成 spring-boot-starter-aoppom.xml 核心依赖如下可直接复制!-- Spring Boot 核心依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- Spring AOP 依赖Spring Boot 自动集成可省略但建议显式引入清晰 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency!-- 日志依赖方便格式化输出可选 --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependency步骤 2编写 AOP 切面类核心代码创建切面类LogAspect通过注解定义切面、通知和切入点代码中包含详细注释新手可直接复制使用packagecom.example.aop.demo.aspect;importlombok.extern.slf4j.Slf4j;importorg.aspectj.lang.JoinPoint;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.annotation.*;importorg.springframework.stereotype.Component;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;importjava.util.Arrays;/** * 接口日志切面类 * 注解说明 * Aspect标识此类是一个切面类 * Component将切面类交给 Spring 管理必须加否则切面不生效 */Slf4jAspectComponentpublicclassLogAspect{/** * 切入点指定切面作用的范围 * 表达式说明 * execution(* com.example.aop.demo.controller..*(..)) * 1. *返回值任意 * 2. com.example.aop.demo.controller包名可替换为你的 controller 包路径 * 3. ..当前包及子包下的所有类 * 4. *(..)所有方法任意参数、任意方法名 */Pointcut(execution(* com.example.aop.demo.controller..*(..)))publicvoidlogPointCut(){// 切入点方法无需实现任何逻辑仅用于定义切入点表达式}/** * 1. 前置通知Before在目标方法执行前执行 * 作用记录请求的基础信息URL、IP、请求方法、参数 */Before(logPointCut())publicvoiddoBefore(JoinPointjoinPoint){// 获取请求上下文ServletRequestAttributesattributes(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequestrequestattributes.getRequest();// 打印请求信息log.info( 接口请求开始 );log.info(请求URL{},request.getRequestURI());log.info(请求方法{},request.getMethod());log.info(请求IP{},request.getRemoteAddr());log.info(目标方法{},joinPoint.getSignature().getDeclaringTypeName().joinPoint.getSignature().getName());log.info(请求参数{},Arrays.toString(joinPoint.getArgs()));}/** * 2. 环绕通知Around在目标方法执行前后都执行可控制目标方法的执行 * 作用计算接口执行耗时最常用场景 * 注意环绕通知必须有返回值且参数是 ProceedingJoinPoint可执行目标方法 */Around(logPointCut())publicObjectdoAround(ProceedingJoinPointproceedingJoinPoint)throwsThrowable{// 记录方法开始执行时间longstartTimeSystem.currentTimeMillis();// 执行目标方法放行让业务方法执行ObjectresultproceedingJoinPoint.proceed();// 计算执行耗时longendTimeSystem.currentTimeMillis();log.info(接口执行耗时{} ms,endTime-startTime);// 返回目标方法的执行结果必须返回否则接口会返回 nullreturnresult;}/** * 3. 后置通知After无论目标方法是否抛出异常都会执行 * 作用标记请求结束做一些收尾工作 */After(logPointCut())publicvoiddoAfter(){log.info( 接口请求结束 \n);}/** * 4. 后置返回通知AfterReturning目标方法正常执行完成后执行 * 作用记录接口返回结果 * returning result指定返回值的参数名必须和方法参数一致 */AfterReturning(pointcutlogPointCut(),returningresult)publicvoiddoAfterReturning(Objectresult){log.info(接口返回结果{},result);}/** * 5. 后置异常通知AfterThrowing目标方法抛出异常时执行 * 作用记录异常信息方便排查问题 * throwing e指定异常参数名必须和方法参数一致 */AfterThrowing(pointcutlogPointCut(),throwinge)publicvoiddoAfterThrowing(Throwablee){log.error(接口执行异常,e);// 打印异常堆栈信息}}}步骤 3编写测试接口验证 AOP 效果创建一个简单的 Controller编写两个接口正常接口 异常接口测试 AOP 是否生效packagecom.example.aop.demo.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;RestControllerpublicclassTestController{// 正常接口模拟查询用户GetMapping(/user/get)publicStringgetUser(RequestParamStringusername){return查询成功用户username;}// 异常接口模拟抛出异常PostMapping(/user/error)publicvoidtestError(){inti1/0;// 除数为0抛出异常}}步骤 4启动项目测试效果1. 启动 Spring Boot 项目访问正常接口http://localhost:8080/user/get?usernamezhangsan控制台输出AOP 日志生效 接口请求开始 请求URL/user/get 请求方法GET 请求IP0:0:0:0:0:0:0:1 目标方法com.example.aop.demo.controller.TestController.getUser 请求参数[zhangsan] 接口返回结果查询成功用户zhangsan 接口执行耗时12 ms 接口请求结束 2. 访问异常接口http://localhost:8080/user/error控制台输出异常日志生效 接口请求开始 请求URL/user/error 请求方法POST 请求IP0:0:0:0:0:0:0:1 目标方法com.example.aop.demo.controller.TestController.testError 请求参数[] 接口执行异常 java.lang.ArithmeticException: / by zero at com.example.aop.demo.controller.TestController.testError(TestController.java:18) ...省略异常堆栈 接口请求结束 至此AOP 实例演示完成完美实现了接口日志的统一记录且没有侵入任何业务代码TestController 中没有任何日志相关代码。三、AOP 三种实现方式从简单到复杂上面实例用的是「注解式实现」最常用除此之外Spring AOP 还有另外两种实现方式适合不同场景逐一说明方式 1注解式实现Aspect 注解通知就是上面实例用的方式也是目前 Spring Boot 项目中最推荐的方式优点代码简洁无需配置文件上手快灵活度高切入点表达式可精准控制作用范围支持所有 5 种通知类型。核心要点必须加Aspect和Component注解切入点用Pointcut定义通知用Before、Around等注解。方式 2XML 配置式实现传统方式在 Spring 早期没有注解主要用 XML 配置实现 AOP现在很少用但了解即可适合老项目维护。步骤1. 编写切面类无需加任何 AOP 注解2. 在 spring.xml 中配置切面、切入点、通知。示例简化版!-- 1. 配置切面类 --beanidlogAspectclasscom.example.aop.demo.aspect.LogAspect/!-- 2. 配置 AOP --aop:config!-- 配置切入点 --aop:pointcutidlogPointCutexpressionexecution(* com.example.aop.demo.controller..*(..))/!-- 配置切面关联切入点和通知 --aop:aspectreflogAspectaop:beforepointcut-reflogPointCutmethoddoBefore/aop:aroundpointcut-reflogPointCutmethoddoAround/aop:afterpointcut-reflogPointCutmethoddoAfter//aop:aspect/aop:config缺点XML 配置繁琐维护成本高不推荐新项目使用。方式 3实现接口式实现最原始方式通过实现 Spring 提供的 AOP 接口如MethodBeforeAdvice、AfterReturningAdvice来实现通知仅支持部分通知类型无环绕通知灵活性差几乎不用。示例简化版// 实现前置通知接口ComponentpublicclassLogBeforeAdviceimplementsMethodBeforeAdvice{Overridepublicvoidbefore(Methodmethod,Object[]args,Objecttarget)throwsThrowable{// 前置通知逻辑和 Before 一样log.info(前置通知方法执行前执行);}}// XML 配置切入点省略和方式2类似总结优先使用 注解式实现XML 方式仅用于老项目维护接口式实现可忽略。四、AOP 核心注意事项避坑关键重中之重很多新手用 AOP 时会遇到“切面不生效”“通知执行顺序错乱”等问题以下注意事项必须牢记覆盖 90% 的坑1. 切面类必须交给 Spring 管理必坑切面类上必须加Component或Service、Controller否则 Spring 无法扫描到切面AOP 完全不生效。错误示例只加Aspect不加Component→ 切面无效。2. 切入点表达式必须正确必坑切入点表达式写错切面会作用到错误的方法甚至完全不生效常见错误包路径写错比如 controller 包路径少写一层表达式语法错误比如少写\.\.、\*位置错误目标方法是 private 修饰 → AOP 无法拦截AOP 只能拦截 public 方法。推荐写完切入点后先测试一个简单接口验证切面是否生效。3. 环绕通知的注意事项高频坑必须调用proceedingJoinPoint\.proceed\(\)否则目标方法不会执行相当于拦截了方法不放行必须返回proceed\(\)的结果否则接口会返回 null即使目标方法有返回值环绕通知可以修改目标方法的参数和返回值灵活但需谨慎。4. 通知执行顺序重点当一个方法被多个切面拦截或一个切面有多个通知时执行顺序如下牢记正常情况前置通知Before→ 环绕通知proceed() 前→ 目标方法 → 环绕通知proceed() 后→ 后置返回通知AfterReturning→ 后置通知After异常情况前置通知 → 环绕通知proceed() 前→ 目标方法抛异常→ 后置异常通知AfterThrowing→ 后置通知After补充多个切面的执行顺序可通过Order注解控制值越小执行越早。5. AOP 无法拦截的情况高频坑private、final、static 修饰的方法AOP 基于动态代理无法代理这些方法方法内部调用比如同一个类中方法 A 调用方法 B方法 B 被切面拦截 → 不生效目标对象没有交给 Spring 管理比如 new 出来的对象不是 Spring 容器中的 bean。解决方案内部方法调用时通过 Spring 容器获取自身 bean再调用方法避免直接 this.方法名()。6. 性能注意事项切面逻辑尽量简单比如日志记录、参数校验避免复杂计算影响接口性能切入点范围尽量精准比如只作用于 controller 层不要作用于所有层减少拦截次数。7. 注解式切入点补充更灵活除了 execution 表达式还可以用自定义注解作为切入点更灵活比如只给需要日志的接口加注解// 1. 定义自定义注解Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceLogAnnotation{// 可添加属性比如日志描述Stringvalue()default;}// 2. 切入点改为注解Pointcut(annotation(com.example.aop.demo.annotation.LogAnnotation))publicvoidlogPointCut(){}// 3. 在接口上添加注解LogAnnotation(查询用户接口)GetMapping(/user/get)publicStringgetUser(RequestParamStringusername){...}五、AOP 常见应用场景拓展提升博客价值除了日志记录AOP 还有很多实用场景帮你拓展思路权限校验拦截接口判断用户是否登录、是否有操作权限事务管理Spring 声明式事务Transactional底层就是 AOP参数校验统一校验接口请求参数比如非空、格式校验接口限流限制接口的请求频率比如每秒最多10次请求异常统一处理全局异常捕获返回统一的错误格式。六、总结AOP 的核心是「解耦」将通用功能抽离成切面不侵入业务代码提升代码复用性和可维护性。本文通过「接口日志」实例讲解了注解式实现最常用、XML 实现、接口式实现三种方式同时总结了新手常踩的坑和注意事项。对于 CSDN 博客读者来说本文代码可直接复制到项目中使用同时理解核心原理后续可根据实际场景如权限、限流灵活修改切面逻辑。最后留一个小思考如何用 AOP 实现接口限流欢迎在评论区交流