0

0

基于SpringBoot使用AOP技术实现操作日志管理[通俗易懂]

星夢妙者

星夢妙者

发布时间:2025-07-14 10:26:13

|

955人浏览过

|

来源于php中文网

原创

大家好,我们又见面了,我是你们的朋友全栈君。

本文将详细探讨AOP(面向切面编程)的基本概念、基本应用以及日志管理的实际操作。掌握了这些内容,你将轻松运用AOP技术。

一、基本概念

以下是AOP的一些核心概念:

Aspect(切面)

切面是将关注点模块化的方式,它包括通知和切点。切面定义了其功能的内容、执行的时机和地点。例如,事务管理和日志记录可以视为切面。

Join point(连接点)

连接点是程序执行中的一个点,如方法调用或异常处理。

Advice(通知)

通知是在特定连接点上执行的动作。

Pointcut(切点)

切点是一个匹配连接点的表达式。通知与切点表达式关联,并在匹配的连接点上执行。Spring使用AspectJ切点表达式语言作为默认。

Introduction(引入)

引入允许为类型添加新的方法或字段。Spring AOP允许你为被建议的对象引入新的接口,例如,使bean实现IsModified接口以简化缓存。

Target object(目标对象)

目标对象是被一个或多个切面通知的对象。由于Spring AOP使用运行时代理实现,因此目标对象始终是代理对象。

AOP proxy(AOP代理)

AOP代理是由AOP框架创建的对象,用于实现切面契约。Spring中,AOP代理可以是JDK动态代理或CGLIB代理。

Weaving(织入)

Q.AI视频生成工具
Q.AI视频生成工具

支持一分钟生成专业级短视频,多种生成方式,AI视频脚本,在线云编辑,画面自由替换,热门配音媲美真人音色,更多强大功能尽在QAI

下载

织入是将通知添加到目标类连接点的过程,可以在编译时、加载时或运行时完成。

Spring支持以下五种类型的通知:

  • 前置通知(Before):在目标方法调用之前执行。
  • 后置通知(After):在目标方法完成后执行,无论是正常结束还是异常退出。
  • 返回通知(After-returning):在目标方法成功执行后执行。
  • 异常通知(After-throwing):在目标方法抛出异常后执行。
  • 环绕通知(Around):在目标方法调用前后执行自定义行为。

通知的执行顺序如下:

基于SpringBoot使用AOP技术实现操作日志管理[通俗易懂]基于SpringBoot使用AOP技术实现操作日志管理[通俗易懂]

后续的基本应用部分将展示如何实现这些通知,并演示它们的执行顺序。

二、基本应用

下面是声明通知的示例代码,你可以复制并运行以验证通知的执行顺序:

@Aspect
public class Test {
    private static int step = 0;
<pre class="brush:php;toolbar:false;"><code>@Pointcut("@annotation(com.chenyanwu.erp.erpframework.annotation.Log)")
private void operation() {}

@Before("operation()")
public void doBeforeTask() {
    System.out.println(++step + " 前置通知");
}

@After("operation()")
public void doAfterTask() {
    System.out.println(++step + " 后置通知");
}

@AfterReturning(pointcut = "operation()", returning = "retVal")
public void doAfterReturnningTask(Object retVal) {
    System.out.println(++step + " 返回通知,返回值为:" + retVal.toString());
}

@AfterThrowing(pointcut = "operation()", throwing = "ex")
public void doAfterThrowingTask(Exception ex) {
    System.out.println(++step + " 异常通知,异常信息为:" + ex.getMessage());
}

@Around("operation()")
public Object doAroundTask(ProceedingJoinPoint pjp) {
    String methodname = pjp.getSignature().getName();
    Object result = null;
    try {
        // 前置通知
        System.out.println("目标方法" + methodname + "开始,参数为" + Arrays.asList(pjp.getArgs()));
        // 执行目标方法
        result = pjp.proceed();
        // 返回通知
        System.out.println("目标方法" + methodname + "执行成功,返回" + result);
    } catch (Throwable e) {
        // 异常通知
        System.out.println("目标方法" + methodname + "抛出异常: " + e.getMessage());
    }
    // 后置通知
    System.out.println("目标方法" + methodname + "结束");
    return result;
}

}

注意切入点的表达式格式:

  • 修饰符匹配(modifier-pattern?)
  • 返回值匹配(ret-type-pattern),可以是表示任何返回值
  • 类路径匹配(declaring-type-pattern?)
  • 方法名匹配(name-pattern),可以是具体方法名或代表所有方法,或set代表所有以set开头的方法
  • 参数匹配((param-pattern)),可以指定具体参数类型,多个参数用","隔开,表示任意类型的参数
  • 异常类型匹配(throws-pattern?)

三、日志管理实战

基于对基本应用的理解,我们直接展示日志管理的实现代码:

  1. 依赖的jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  1. 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
  1. 实现切面
@Aspect
@Order(5)
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Autowired
private ErpLogService logService;
@Autowired
ObjectMapper objectMapper;
private ThreadLocal<Date> startTime = new ThreadLocal<Date>();</p><pre class="brush:php;toolbar:false;"><code>@Pointcut("@annotation(com.chenyanwu.erp.erpframework.annotation.Log)")
public void pointcut() {}

@Before("pointcut()")
public void doBefore(JoinPoint joinPoint) {
    startTime.set(new Date());
}

@AfterReturning(pointcut = "pointcut()", returning = "rvt")
public void doAfter(JoinPoint joinPoint, Object rvt) throws Exception {
    handleLog(joinPoint, null, rvt);
}

@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void doAfter(JoinPoint joinPoint, Exception e) throws Exception {
    handleLog(joinPoint, e, null);
}

@Async
private void handleLog(final JoinPoint joinPoint, final Exception e, Object rvt) throws Exception {
    Method method = getMethod(joinPoint);
    Log log = getAnnotationLog(method);
    if (log == null) {
        return;
    }
    Date now = new Date();
    ErpLog erpLog = new ErpLog();
    erpLog.setErrorCode(0);
    erpLog.setIsDeleted(0);
    HttpServletRequest request = ToolUtil.getRequest();
    erpLog.setType(ToolUtil.isAjaxRequest(request) ? "Ajax请求" : "普通请求");
    erpLog.setTitle(log.value());
    erpLog.setHost(request.getRemoteHost());
    erpLog.setUri(request.getRequestURI().toString());
    erpLog.setHttpMethod(request.getMethod());
    erpLog.setClassMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
    Object[] args = joinPoint.getArgs();
    LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
    String[] paramNames = u.getParameterNames(method);
    if (args != null && paramNames != null) {
        StringBuilder params = new StringBuilder();
        params = handleParams(params, args, Arrays.asList(paramNames));
        erpLog.setParams(params.toString());
    }
    String retString = JsonUtil.bean2Json(rvt);
    erpLog.setResponseValue(retString.length() > 5000 ? JsonUtil.bean2Json("请求参数数据过长不与显示") : retString);
    if (e != null) {
        erpLog.setErrorCode(1);
        erpLog.setErrorMessage(e.getMessage());
    }
    Date stime = startTime.get();
    erpLog.setStartTime(stime);
    erpLog.setEndTime(now);
    erpLog.setExecuteTime(now.getTime() - stime.getTime());
    erpLog.setUsername(MySysUser.loginName());
    HashMap<String, String> browserMap = ToolUtil.getOsAndBrowserInfo(request);
    erpLog.setOperatingSystem(browserMap.get("os"));
    erpLog.setBrower(browserMap.get("browser"));
    erpLog.setId(IdUtil.simpleUUID());
    logService.insertSelective(erpLog);
}

private Log getAnnotationLog(Method method) {
    if (method != null) {
        return method.getAnnotation(Log.class);
    }
    return null;
}

private Method getMethod(JoinPoint joinPoint) {
    Signature signature = joinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    if (method != null) {
        return method;
    }
    return null;
}

private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) throws JsonProcessingException {
    for (int i = 0; i < args.length; i++) {
        if (args[i] instanceof Map) {
            Set set = ((Map) args[i]).keySet();
            List list = new ArrayList();
            List paramList = new ArrayList();
            for (Object key : set) {
                list.add(((Map) args[i]).get(key));
                paramList.add(key);
            }
            return handleParams(params, list.toArray(), paramList);
        } else {
            if (args[i] instanceof Serializable) {
                Class<?> aClass = args[i].getClass();
                try {
                    aClass.getDeclaredMethod("toString", new Class<?>[]{null});
                    params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                } catch (NoSuchMethodException e) {
                    params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                }
            } else if (args[i] instanceof MultipartFile) {
                MultipartFile file = (MultipartFile) args[i];
                params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
            } else {
                params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
            }
        }
    }
    return params;
}

}

  1. 对应代码添加注解
@Log("新增学生")
@RequestMapping(value = "/create", method = RequestMethod.POST)
@ResponseBody
public ResultBean<String> create(@RequestBody @Validated ErpStudent item) {
if(service.insertSelective(item) == 1) {
insertErpSFamilyMember(item);
return new ResultBean<String>("");
}
return new ResultBean<String>(ExceptionEnum.BUSINESS_ERROR, "新增学生异常!", "新增失败!", "");
}

在执行业务操作后,日志将被写入数据库。你可以通过界面查询日志记录:

基于SpringBoot使用AOP技术实现操作日志管理[通俗易懂]

完整的日志管理代码可在GitHub上获取:https://www.php.cn/link/201d7a97aad11bd86a94e02edd95bebb

发布者:全栈程序员栈长,转载请注明出处:https://www.php.cn/link/4b73c0453607648d723ab59b5905f61d

原文链接:https://www.php.cn/link/c8377ad2a50fb65de28b11cfc628d75c

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

154

2025.08.06

Java Spring Security 与认证授权
Java Spring Security 与认证授权

本专题系统讲解 Java Spring Security 框架在认证与授权中的应用,涵盖用户身份验证、权限控制、JWT与OAuth2实现、跨站请求伪造(CSRF)防护、会话管理与安全漏洞防范。通过实际项目案例,帮助学习者掌握如何 使用 Spring Security 实现高安全性认证与授权机制,提升 Web 应用的安全性与用户数据保护。

88

2026.01.26

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1848

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

614

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2357

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

47

2026.01.19

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

435

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

601

2023.08.10

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

23

2026.03.06

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
成为PHP架构师-自制PHP框架
成为PHP架构师-自制PHP框架

共28课时 | 2.6万人学习

YMP在线手册
YMP在线手册

共64课时 | 49万人学习

Git 教程
Git 教程

共21课时 | 4.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号