首页 > 运维 > linux运维 > 正文

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

星夢妙者
发布: 2025-07-14 10:26:13
原创
910人浏览过

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

本文将详细探讨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代理。

沁言学术
沁言学术

你的论文写作AI助理,永久免费文献管理工具,认准沁言学术

沁言学术30
查看详情 沁言学术

Weaving(织入)

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

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

以上就是基于SpringBoot使用AOP技术实现操作日志管理[通俗易懂]的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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