🚀 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)

2698

被折叠的 条评论
为什么被折叠?



