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(织入)

TalkMe
TalkMe

与AI语伴聊天,练习外语口语

下载

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

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

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

通知的执行顺序如下:

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

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

二、基本应用

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

@Aspect
public class Test {
    private static int step = 0;
@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包

org.springframework.boot
spring-boot-starter-aop
  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 startTime = new ThreadLocal();

@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() youjiankuohaophpcn 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());
    HashMapzuojiankuohaophpcnString, Stringyoujiankuohaophpcn 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 zuojiankuohaophpcn 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) {
                Classzuojiankuohaophpcn?youjiankuohaophpcn aClass = args[i].getClass();
                try {
                    aClass.getDeclaredMethod("toString", new Classzuojiankuohaophpcn?youjiankuohaophpcn[]{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 create(@RequestBody @Validated ErpStudent item) {
if(service.insertSelective(item) == 1) {
insertErpSFamilyMember(item);
return new ResultBean("");
}
return new ResultBean(ExceptionEnum.BUSINESS_ERROR, "新增学生异常!", "新增失败!", "");
}

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

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

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

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

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

相关专题

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

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

103

2025.08.06

html版权符号
html版权符号

html版权符号是“©”,可以在html源文件中直接输入或者从word中复制粘贴过来,php中文网还为大家带来html的相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

616

2023.06.14

html在线编辑器
html在线编辑器

html在线编辑器是用于在线编辑的工具,编辑的内容是基于HTML的文档。它经常被应用于留言板留言、论坛发贴、Blog编写日志或等需要用户输入普通HTML的地方,是Web应用的常用模块之一。php中文网为大家带来了html在线编辑器的相关教程、以及相关文章等内容,供大家免费下载使用。

654

2023.06.21

html网页制作
html网页制作

html网页制作是指使用超文本标记语言来设计和创建网页的过程,html是一种标记语言,它使用标记来描述文档结构和语义,并定义了网页中的各种元素和内容的呈现方式。本专题为大家提供html网页制作的相关的文章、下载、课程内容,供大家免费下载体验。

470

2023.07.31

html空格
html空格

html空格是一种用于在网页中添加间隔和对齐文本的特殊字符,被用于在网页中插入额外的空间,以改变元素之间的排列和对齐方式。本专题为大家提供html空格的相关的文章、下载、课程内容,供大家免费下载体验。

245

2023.08.01

html是什么
html是什么

HTML是一种标准标记语言,用于创建和呈现网页的结构和内容,是互联网发展的基石,为网页开发提供了丰富的功能和灵活性。本专题为大家提供html相关的各种文章、以及下载和课程。

2895

2023.08.11

html字体大小怎么设置
html字体大小怎么设置

在网页设计中,字体大小的选择是至关重要的。合理的字体大小不仅可以提升网页的可读性,还能够影响用户对网页整体布局的感知。php中文网将介绍一些常用的方法和技巧,帮助您在HTML中设置合适的字体大小。

505

2023.08.11

html转txt
html转txt

html转txt的方法有使用文本编辑器、使用在线转换工具和使用Python编程。本专题为大家提供html转txt相关的文章、下载、课程内容,供大家免费下载体验。

312

2023.08.31

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

3

2026.01.19

热门下载

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

精品课程

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

共28课时 | 2.4万人学习

YMP在线手册
YMP在线手册

共64课时 | 36.9万人学习

Git 教程
Git 教程

共21课时 | 2.8万人学习

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

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