开发者

SpringBoot实现AOP日志切面功能的详细教程

目录
  • Spring Boot 实现AOP日志切面全流程教程
    • 效果
    • 一、引入依赖
    • 二、配置AOP属性(可选)
    • 三、定义切点(Pointcut)
    • 四、实现切面(ASPect)
      • 说明
    • 五、自定义注解与切面
      • 六、Service层切面实现
        • 七、完整流程总结
          • 八、常见问题
            • AOP 的主要好处
              • 关注点分离(Separation of Concerns)
              • 避免重复代码,提高可维护性
              • 不侵入业务代码
              • 增强系统的可观测性与调试能力
              • 灵活可配置
              • 提高开发效率
            • 总结

            Spring Boot 实现AOP日志切面全流程教程

            效果

            切入com.anfioo下的所有controller层

            SpringBoot实现AOP日志切面功能的详细教程

            切入com.anfioo下的所有service层

            SpringBoot实现AOP日志切面功能的详细教程

            切入自定义注解@LogRecord

            SpringBoot实现AOP日志切面功能的详细教程

            SpringBoot实现AOP日志切面功能的详细教程

            全都开启

            SpringBoot实现AOP日志切面功能的详细教程

            可以使用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;
                }
            }
            
            

            七、完整流程总结

            1. 引入依赖:确保spring-boot-starter-aop已添加
            2. 配置属性:通过配置文件灵活控制切面开关
            3. 定义切点:用表达式或注解指定拦截范围
            4. 实现切面:用@Aspect@Around等实现日志逻辑
            5. 自定义注解:实现更细粒度的日志控制
            6. 应用注解:在需要记录日志的方法上加上自定义注解

            八、常见问题

            • 切面不生效? 检查@Component@Aspect是否加上,切点表达式是否正确,AOP依赖是否引入。
            • 日志打印不全? 检查日志级别、切面逻辑是否被条件限制跳过。

            AOP 的主要好处

            关注点分离(Separation of Concerns)

            AOP 允许你将与业务逻辑无关的“横切关注点”(如日志记录、安全控制、异常处理、事务管理)从核心业务代码中分离出去,使业务逻辑更专注、更清晰。

            避免重复代码,提高可维护性

            常见的重复操作如打印日志、性能统计、权限校验,如果分散在各个方法中,会导致维护困难。AOP 将这些重复逻辑集中在一个地方,修改一次即可生效全局。

            不侵入业务代码

            通过 AOP,你可以在不修改原方法的前提下,增强其功能(如记录参数、返回值、异常信息等),实现“开闭原则”:对扩展开放,对修改封闭。

            增强系统的可观测性与调试能力

            配合 AOP 自动记录方法调用轨迹、执行耗时、输入输出信息,极大提升问题排查和性能分析的效率。

            灵活可配置

            结合注解、自定义属性、开关控制(如 AopDebugProperties),你可以根据环境或条件动态启用/禁用某些切面逻辑,适应多种部署或调试场景。

            提高开发效率

            开发人员无需手动添加日志、异常处理等模板式代码,只需专注于业务逻辑,其余交由统一切面处理,显著提高开发效率和代码一致性。

            总结

            AOP 帮你在“不碰业务代码”的前提下,实现系统级增强,让代码更干净、功能更强大、维护更轻松。

            通过AOP切面,你可以优雅地实现日志记录、权限校验等横切关注点,极大提升代码的可维护性和可扩展性。希望本文能帮助你快速上手Spring Boot的AOP切面开发!

            以上就是SpringBoot实现AOP日志切面功能的详细教程的详细内容,更多关于SpringBoot AOP日志切面的资料请关注编程客栈(www.devze.com)其它相关文章!

            0

            上一篇:

            下一篇:

            精彩评论

            暂无评论...
            验证码 换一张
            取 消

            最新开发

            开发排行榜