【注解+aop 记录日志】关于如何使用自定义 @Log 注解实现 Spring AOP 操作日志记录功能的详细教程

🚀 Spring AOP + 自定义注解实现操作日志记录功能教程


🟢 一、功能简介与优势

✅ 简单用法(示例)

@DeleteMapping("/{NewsIds}")
@Log(title = "【新闻信息】", businessType = BusinessType.DELETE)
public AjaxResult remove(@PathVariable Long[] NewsIds) {
    return toAjax(appNewsService.deleteAppNewsByNewsIds(NewsIds));
}

只需在方法上添加 @Log 注解,系统就会自动记录调用的参数、返回值、模块名、操作类型等日志信息,无需手动写日志。


🎯 优势亮点

优势说明
✅ 解耦日志逻辑日志记录逻辑通过 AOP 与业务代码解耦,统一管理
✅ 简单统一一句注解搞定所有日志,开发成本低
✅ 自动化自动记录请求参数、返回值、异常等
✅ 高度可扩展可按需拓展日志记录方式(保存数据库、ES、链路追踪等)
✅ 支持排除参数可配置排除敏感字段,如密码、Token 等

二、实现步骤与代码讲解


1️⃣ 定义日志注解 @Log

@Target({ ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Log { /** 模块标题 */ String title() default ""; /** 功能类型(参考 BusinessType 枚举) */ BusinessType businessType() default BusinessType.OTHER; /** 操作人类别(默认后台管理用户) */ OperatorType operatorType() default OperatorType.MANAGE; /** 是否保存请求参数 */ boolean isSaveRequestData() default true; /** 是否保存响应结果 */ boolean isSaveResponseData() default true; /** 排除指定的参数名(如 password) */ String[] excludeParamNames() default {}; } 

2️⃣ 定义业务类型枚举 BusinessType

public enum BusinessType {
    OTHER,      // 其它
    INSERT,     // 新增
    UPDATE,     // 修改
    DELETE,     // 删除
    SELECT,     // 查询
    EXPORT,     // 导出
    IMPORT,     // 导入
    LOGIN,      // 登录
    LOGOUT      // 登出
}
 

可根据实际项目拓展 BusinessType 的枚举项。


3️⃣ 编写切面类 LogAspect

@Aspect @Component public class LogAspect { private static final Logger logger = LoggerFactory.getLogger(LogAspect.class); /** 切入点:拦截所有 @Log 注解的方法 */ @Pointcut("@annotation(com.elinesign.common.annotation.Log)") public void logPointCut() {} /** 正常返回通知:记录操作日志 */ @AfterReturning(pointcut = "logPointCut()", returning = "result") public void doAfterReturning(JoinPoint joinPoint, Object result) { handleLog(joinPoint, null, result); } /** 异常通知:记录异常日志 */ @AfterThrowing(pointcut = "logPointCut()", throwing = "e") public void doAfterThrowing(JoinPoint joinPoint, Exception e) { handleLog(joinPoint, e, null); } /** 核心日志处理逻辑 */ protected void handleLog(final JoinPoint joinPoint, final Exception e, Object result) { try { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Log log = method.getAnnotation(Log.class); if (log == null) return; // 提取日志信息 String title = log.title(); String businessType = log.businessType().name(); String className = joinPoint.getTarget().getClass().getName(); String methodName = signature.getName(); Object[] args = joinPoint.getArgs(); String params = java.util.Arrays.deepToString(args); String resultStr = result != null ? result.toString() : "null"; // 打印日志 if (e != null) { logger.error("[操作日志-异常] 标题: {} | 类型: {} | 类: {} | 方法: {} | 参数: {}", title, businessType, className, methodName, params, e); } else { logger.info("[操作日志] 标题: {} | 类型: {} | 类: {} | 方法: {} | 参数: {} | 返回: {}", title, businessType, className, methodName, params, resultStr); } } catch (Exception ex) { logger.error("日志切面处理异常", ex); } } } 

4️⃣ 实际使用示例

@PostMapping("/add") @Log(title = "新增用户", businessType = BusinessType.INSERT) public AjaxResult addUser(@RequestBody User user) { return userService.addUser(user); } 

执行该接口后,日志会自动记录类似:

[操作日志] 标题: 新增用户 | 类型: INSERT | 类: com.example.controller.UserController | 方法: addUser | 参数: [User{id=1, name=张三}] | 返回: AjaxResult{code=200, msg='成功'}


📌 三、可选增强(推荐)

  • ✔️ 将日志写入数据库,可定义 SysLog 实体 + LogService 服务类

  • ✔️ 日志异步处理(防止主线程卡顿)

  • ✔️ 过滤敏感字段(如 password、token)

  • ✔️ 记录请求 IP、当前登录用户、请求 URL 等信息


✅ 四、总结

特性说明
注解驱动一行注解,统一记录日志
非侵入式不影响原有业务逻辑
易于维护统一处理、集中管理
易于拓展可落库、异步处理、链路追踪

🔧 五、实现原理概述

该日志记录功能的实现依赖于:

  • ✅ 自定义注解 @Log:用于标识哪些方法需要记录操作日志;

  • ✅ Spring AOP(面向切面编程):用于在方法执行 前/后/异常时 拦截标注了 @Log 注解的方法;

  • ✅ 通过 AOP 拿到方法上下文(类名、方法名、参数、返回值、异常等);

  • ✅ 利用日志框架(如 SLF4J + Logback)进行输出。


🧠 六、关键技术原理详解

🔹 1. Spring AOP 的工作原理

Spring AOP(Aspect-Oriented Programming)允许我们在不修改业务代码的前提下,向特定方法添加额外逻辑,比如日志、事务、权限控制等。

在这套日志功能中:

  • @Aspect 定义了一个切面类 LogAspect

  • @Pointcut 指定了切入点:所有带 @Log 注解的方法

  • @AfterReturning 表示:方法正常返回后执行日志记录

  • @AfterThrowing 表示:方法抛出异常时也记录日志

💡 核心机制依赖于 Spring 的代理模式(JDK 动态代理或 CGLIB 代理),将目标类封装在代理对象中,执行时插入横切逻辑。


🔹 2. 注解驱动设计原理

你定义了一个注解 @Log,它本身不会产生任何效果,但通过 AOP:

Log log = method.getAnnotation(Log.class);

这个逻辑会在运行时通过反射读取方法上的注解内容(如标题、操作类型等),然后根据注解配置打印日志。

这是一种声明式编程模式,开发者只需要声明注解,不必处理日志逻辑。


🔹 3. JoinPoint 作用

public void handleLog(JoinPoint joinPoint, Exception e, Object result)

JoinPoint 提供了方法执行的上下文信息,包括:

信息示例
类名com.example.controller.AppNewsController
方法名remove
参数列表[1,2,3]
返回结果AjaxResult
异常信息NullPointerException

这使得你可以记录谁在什么时候操作了什么内容。


🔹 4. 注解 + AOP 的好处

  • 解耦日志逻辑与业务代码

  • 统一记录风格,方便后期接入数据库、审计平台

  • 可插拔性强:不想记录某方法日志,只需移除注解


🖇️ 七、执行流程图(简化)

Controller 方法被调用
     ↓
Spring AOP 检查该方法是否有 @Log 注解
     ↓ 是
LogAspect 拦截该方法:
     - 方法执行成功 → 执行 doAfterReturning()
     - 方法抛出异常 → 执行 doAfterThrowing()
     ↓
handleLog() 获取方法上下文 + 注解内容
     ↓
打印日志(或写入数据库)


🧭 八、常见问题解答

❓ Q1: 为什么使用注解而不是统一拦截所有方法?

答:使用注解可以更精准地控制哪些方法需要记录日志,避免无效日志信息干扰。


❓ Q2: 为什么不在 Controller 里直接写 logger.info()

答:AOP 的优势在于统一处理、解耦逻辑、不影响原有业务代码,增强系统可维护性与可读性。


❓ Q3: 方法参数是对象怎么办?会打印太多吗?

答:可以

  • excludeParamNames 排除敏感字段

  • 或扩展参数打印逻辑,对对象参数进行序列化再打印(如转 JSON)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值