SpringBoot实现AOP日志切面功能的详细教程
目录
- Spring Boot 实现AOP日志切面全流程教程
- 效果
- 一、引入依赖
- 二、配置AOP属性(可选)
- 三、定义切点(Pointcut)
- 四、实现切面(ASPect)
- 说明
- 五、自定义注解与切面
- 六、Service层切面实现
- 七、完整流程总结
- 八、常见问题
- AOP 的主要好处
- 关注点分离(Separation of Concerns)
- 避免重复代码,提高可维护性
- 不侵入业务代码
- 增强系统的可观测性与调试能力
- 灵活可配置
- 提高开发效率
- 总结
Spring Boot 实现AOP日志切面全流程教程
效果
切入com.anfioo下的所有controller层
切入com.anfioo下的所有service层
切入自定义注解@LogRecord
全都开启
可以使用aop切片更好的打印这个方法信息,而不污染原有的方法
一、引入依赖
首先,确保你的Spring Boot项目已经引入了AOP相关依赖。Spring Boot Starter通常已经包含了AOP依赖,但你可以在pom.XML
中显式添加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
二、配置AOP属性(可选)
为了灵活控制切面的开启与关闭,我们可以通过配置文件添加自定义属性。例如:
# application.yaml aop: debug: controller: true # 控制Contr编程客栈oller切面是否开启 service: false # 控制Service切面是否开启 log-record: true # 控制LogRecord注解切面是否开启
并通过@ConfigurationProperties
将其绑定到Java类:
@Data @Component @ConfigurationProperties(prefix = "aop.debug") public class AopDebugProperties { private Boolean controller = true; private Boolean service = false; private Boolean logRecord = true; }
三、定义切点(Pointcut)
切点用于指定哪些类或方法会被AOP拦截。常见的切点表达式有:
execution(public * com.example..controller..*(..))
:拦截所有controller包下的公共方法@annotation(com.example.LogRecord)
:拦截所有被自定义注解标记的方法
四、实现切面(Aspect)
以Controller层日志为例,实现一个切面类:
package com.anfioo.common.log; import com.anfioo.common.bean.AopDebugProperties; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Loggejavascriptr; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * 全局 Controller 层日志记录切面 */ @Aspect @Component @Slf4j public class ControllerLogAspect { @Autowired private AopDebugProperties aopDebugProperties; /** * 定义切点,匹配 com.anfioo 包下所有子包中的 controller 类的公共方法 */ @Pointcut("execution(public * com.anfioo..controller..*(..))") public void controllerMethods() { } /** * 环绕通知,用于记录 controller 层方法的请求和响应信息 * * @param joinPoint 切入点对象,包含被拦截方法的信息 * @return 被拦截方法的执行结果 * @throws Throwable 如果执行过程中出现异常 */ @Around("controllerMethods()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { if (Boolean.FALSE.equals(aopDebugProperties.getController())) { // 不开启,直接执行原方法 return joinPoint.proceed(); } // 记录开始时间 long start = System.currentTimeMillis(); // 获取目标类的 Class 对象 Class<?> targetClass = joinPoint.getTarget().getClass(); // 获取目标类的 Logger 对象 Logger logger = LoggerFactory.getLogger(targetClass); // 获取当前请求的 HttpServletRequest 对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 提取请求的 URL、方法、IP 地址、处理方法和参数 String url = request.getRequestURL().toString(); String method = request.getMethod(); String ip = request.getRemoteAddr(); String classMethod = joinPoint.getSignature().toShortString(); Object[] args = joinPoint.getArgs(); String red = "\u001B[31m"; // ANSI 红色 String line = IntStream.range(0, 200) // 200 可以改成任意长度 .mapToObj(i -> "#") .collect(Collectors.joining()); System.out.println(red + line); // 记录请求信息 logger.info("\n====== 请求信息 ======\n" + " URL : {}\n" + " Method : {}\n" + " IP : {}\n" + " Handler : {}\n" + " Parameters : {}\n" + "==========================", url, method, ip, classMethod, Arrays.toString(args)); // 尝试执行目标方法,并记录执行结果或异常信息 Object result; try { result = joinPoint.proceed(); } catch (Exception e) { // 记录异常信息,并重新抛出异常 logger.error("\n====== ❌ 异常信息 ======\n" + " Handler : {}\n" + " Error : {}\n" + "==========================", classMethod, e.getMessage(), e); throw e; } // 记录结束时间,并计算耗时 long end = System.currentTimeMillis(); // 记录响应信息 logger.info("\n====== ✅ 响应信息 ======\n" + " Handler : {}\n" + " Response : {}\n" + "⏱️ 耗时 : {} ms\n" + "==========================", classMethod, result, end - start); return result; } }
说明
@Aspect
:声明该类为切面@Pointcut
:定义切点@Around
:环绕通知,可在方法执行前后插入逻辑ProceedingJoinPoint
:用于获取方法信息、参数、执行目标方法等
五、自定义注解与切面
有时我们希望只对特定方法记录日志,可以自定义注解:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface LogRecord { String value() default ""; }
并实现对应的切面:
package com.anfioo.common.log; import com.anfioo.common.bean.AopDebugProperties; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 记录 @LogRecord 注解标记的方法调用日志 */ @Aspect @Component @Slf4j public class LogRecordAspect { @Autowired private AopDebugProperties aopDebugProperties; /** * 切点:拦截所有带有 @LogRecord 注解的方法 */ @Pointcut("@annotation(com.anfioo.common.log.LogRecord)") public void logRecordMethods() { } /** * 环绕通知:记录方法执行详情 */ js @Around("logRecordMethods() && @annotation(logRecord)") public Object around(ProceedingJoinPoint joinPoint, LogRecord logRecord) throws Throwable { if (Boolean.FALSE.equals(aopDebugProperties.getLogRecord())) { return joinPoint.proceed(); // 不开启则跳过 } long start = System.currentTimeMillis(); Class<?> targetClass = joinPoint.getTarget().getClass(); Logger logger = LoggerFactory.getLogger(targetClass); String description = logRecord.value(); // 注解中的描述信息 String methodName = joinPoint.getSignature().toShortString(); Object[] args = joinPoint.getArgs(); logger.info("\n====== LogRecord 方法调用 ======\n" + " 描述 : {}\n" + javascript " 方法 : {}\n" + " 参数 : {}\n" + "=====================================", description, methodName, Arrays.toString(args)); Object result; try { result = joinPoint.proceed(); } catch (Exception e) { logger.error("\n====== ❌ LogRecord 异常 ======\n" + " 方法 : {}\n" + " 异常信息 : {}\n" + "=====================================", methodName, e.getMessage(), e); throw e; } long end = System.currentTimeMillis(); logger.info("\n====== ✅ LogRecord 返回结果 ======\n" + " 方法 : {}\n" + " 返回值 www.devze.com : {}\n" + "⏱️ 耗时 : {} ms\n" + "=====================================", methodName, result, end - start); return result; } }
六、Service层切面实现
同理,可以为Service层实现切面:
package com.anfioo.common.log; import com.anfioo.common.bean.AopDebugProperties; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; /** * Service 层方法日志切面(记录业务逻辑调用) */ @Aspect @Component @Slf4j public class ServiceLogAspect { @Autowired private AopDebugProperties aopDebugProperties; /** * 切入 com.anfioo 包下所有 service 的方法 */ @Pointcut("execution(* com.anfioo..service..*(..))") public void serviceMethods() { } /** * 环绕通知记录 service 层方法的调用情况 */ @Around("serviceMethods()") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { if (Boolean.FALSE.equals(aopDebugProperties.getService())) { // 不开启,直接执行原方法 return joinPoint.proceed(); } long start = System.currentTimeMillis(); Class<?> targetClass = joinPoint.getTarget().getClass(); Logger logger = LoggerFactory.getLogger(targetClass); String methodName = joinPoint.getSignature().toShortString(); Object[] args = joinPoint.getArgs(); logger.info("\n====== ⚙️ Service 方法调用 ======\n" + " Method : {}\n" + " Parameters : {}\n" + "===============================", methodName, Arrays.toString(args)); Object result; try { result = joinPoint.proceed(); } catch (Exception e) { logger.error("\n====== ⚙️ ❌ Service 异常 ======\n" + " Method : {}\n" + " Error : {}\n" + "=============================", methodName, e.getMessage(), e); throw e; } long end = System.currentTimeMillis(); logger.info("\n====== ⚙️ ✅ Service 返回结果 ======\n" + " Method : {}\n" + " Result : {}\n" + "⏱️ 耗时 : {} ms\n" + "===============================", methodName, result, end - start); return result; } }
七、完整流程总结
- 引入依赖:确保
spring-boot-starter-aop
已添加 - 配置属性:通过配置文件灵活控制切面开关
- 定义切点:用表达式或注解指定拦截范围
- 实现切面:用
@Aspect
、@Around
等实现日志逻辑 - 自定义注解:实现更细粒度的日志控制
- 应用注解:在需要记录日志的方法上加上自定义注解
八、常见问题
- 切面不生效? 检查
@Component
、@Aspect
是否加上,切点表达式是否正确,AOP依赖是否引入。 - 日志打印不全? 检查日志级别、切面逻辑是否被条件限制跳过。
AOP 的主要好处
关注点分离(Separation of Concerns)
AOP 允许你将与业务逻辑无关的“横切关注点”(如日志记录、安全控制、异常处理、事务管理)从核心业务代码中分离出去,使业务逻辑更专注、更清晰。
避免重复代码,提高可维护性
常见的重复操作如打印日志、性能统计、权限校验,如果分散在各个方法中,会导致维护困难。AOP 将这些重复逻辑集中在一个地方,修改一次即可生效全局。
不侵入业务代码
通过 AOP,你可以在不修改原方法的前提下,增强其功能(如记录参数、返回值、异常信息等),实现“开闭原则”:对扩展开放,对修改封闭。
增强系统的可观测性与调试能力
配合 AOP 自动记录方法调用轨迹、执行耗时、输入输出信息,极大提升问题排查和性能分析的效率。
灵活可配置
结合注解、自定义属性、开关控制(如 AopDebugProperties
),你可以根据环境或条件动态启用/禁用某些切面逻辑,适应多种部署或调试场景。
提高开发效率
开发人员无需手动添加日志、异常处理等模板式代码,只需专注于业务逻辑,其余交由统一切面处理,显著提高开发效率和代码一致性。
总结
AOP 帮你在“不碰业务代码”的前提下,实现系统级增强,让代码更干净、功能更强大、维护更轻松。
通过AOP切面,你可以优雅地实现日志记录、权限校验等横切关注点,极大提升代码的可维护性和可扩展性。希望本文能帮助你快速上手Spring Boot的AOP切面开发!
以上就是SpringBoot实现AOP日志切面功能的详细教程的详细内容,更多关于SpringBoot AOP日志切面的资料请关注编程客栈(www.devze.com)其它相关文章!
精彩评论