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

 更新时间:2025年07月09日 10:31:48   作者:Anfioo  
AOP是Spring框架的核心特性之一,常用于日志记录、权限校验、事务管理等场景,本文将手把手教你如何在Spring Boot项目中实现AOP日志切面功能,包括依赖引入、切点定义、切面实现、注解自定义等内容,需要的朋友可以参考下

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   # 控制Controller切面是否开启
    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.Logger;
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() {
    }

    /**
     * 环绕通知:记录方法执行详情
     */
    @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" +
                        "🔧 方法        : {}\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" +
                        "📤 返回值      : {}\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日志切面的资料请关注脚本之家其它相关文章!

相关文章

  • java编程几行代码实现买菜自由

    java编程几行代码实现买菜自由

    这篇文章主要为大家介绍了java编程几行代码实现买菜自由,需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • SpringMVC中的HandlerAdapter解析

    SpringMVC中的HandlerAdapter解析

    这篇文章主要介绍了SpringMVC中的HandlerAdapter解析,HandlerAdapter是一个关键的组件,用于将请求与处理程序方法进行适配和调度,它充当了控制器和处理程序之间的桥梁,负责将请求的参数和处理程序方法进行匹配,并将结果返回给前端,需要的朋友可以参考下
    2023-10-10
  • JAVA中字符串如何与整型数字相加

    JAVA中字符串如何与整型数字相加

    这篇文章主要介绍了JAVA中字符串如何与整型数字相加,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-07-07
  • mapstruct的用法之qualifiedByName示例详解

    mapstruct的用法之qualifiedByName示例详解

    qualifiedByName的意思就是使用这个Mapper接口中的指定的默认方法去处理这个属性的转换,而不是简单的get set,今天通过本文给大家介绍下mapstruct的用法之qualifiedByName示例详解,感兴趣的朋友一起看看吧
    2022-04-04
  • SpringBoot3使用Swagger3的示例详解

    SpringBoot3使用Swagger3的示例详解

    本文介绍了如何在Spring Boot 3项目中使用Swagger3进行后端接口的前端展示,首先,通过添加依赖并配置application.yml文件来快速启动Swagger,然后,详细介绍了Swagger3的新注解与Swagger2的区别,并提供了一些常用注解的使用示例,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • Java继承的实现与继承限制分析

    Java继承的实现与继承限制分析

    这篇文章主要介绍了Java继承的实现与继承限制,结合具体实例形式分析了Java继承的定义、实现以及继承的相关限制,需要的朋友可以参考下
    2019-01-01
  • SpringBoot+Redis防止恶意刷新与暴力请求接口的实现

    SpringBoot+Redis防止恶意刷新与暴力请求接口的实现

    这篇文章主要为大家介绍了如何利用springboot和Redis来实现防止恶意刷新与暴力请求接口,文中的示例代码讲解详细,需要的可以参考一下
    2022-06-06
  • SpringMVC+MyBatis声明式事务管理

    SpringMVC+MyBatis声明式事务管理

    在最近的一个项目中,采用springMVC、mybatis,MySQL、tomcat,事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。Spring Framework对事务管理提供了一致的抽象,
    2015-08-08
  • vue+springboot上传文件、图片、视频及回显到前端详解

    vue+springboot上传文件、图片、视频及回显到前端详解

    一般来说vue可以使用axios或者fetch等ajax库发送文件请求,而springboot则可以使用Spring MVC的方式来处理上传文件请求,下面这篇文章主要给大家介绍了关于vue+springboot上传文件、图片、视频及回显到前端的相关资料,需要的朋友可以参考下
    2023-04-04
  • 学习Java之Java中的异常处理机制详解

    学习Java之Java中的异常处理机制详解

    在本文中,小编将带领大家来学习Java的异常处理机制,包括异常机制、异常类型、如何捕获异常、如何抛出异常以及如何创建自定义异常等核心内容,感兴趣的同学跟着小编一起来看看吧
    2023-08-08

最新评论