Spring AOP实战--之优雅的统一打印web请求的出参和入参

news2024/11/27 22:43:14
  • 背景介绍

由于实际项目内网开发,项目保密,因此本文以笔者自己搭建的demo做演示,方便大家理解。

在项目开发过程中,团队成员为了方便调试,经常会在方法的出口和入口处加上log输出,由于每个人的log需求和输出方式不一样,在测试环境还好,但是上线后导致项目的日志输出特别的杂乱,有时候想要根据日志排查问题就特别地费劲。下面demo是项目中典型的日志输出方式

对于上面的日志打印位置和输出,其实是特别随意不规范的

  • 例如controller层和service层都对请求的入参进行了打印,输出没有什么明显的改变,这个一般可以只保留一个
  • 日志的整个请求缺少链路追踪,如果多个请求过来都打印分不清哪个是哪个

鉴于存在以上的不足,笔者痛下决心决定对日志打印进行改造。

  • 架构思路

由于笔者的项目是微服务集群架构,但是单个服务是遵循MVC分层架构的,如下图所示:

鉴于这样的分层结构,笔者决定从controller层下手,使用spring AOP封装统一的入参和出参日志打印,以规范和解决项目中日志输出的乱象。

  • Spring AOP 核心概念

开始撸代码之前先简单回顾下Spring AOP的相关知识点

1、切面(aspect):切面就是对横切关注点的抽象,被@Aspect标记的类
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
3、连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring
中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut):对连接点进行拦截的定义,可以是切点表达式,也可以是注解
5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象:代理的目标对象
7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程

我们编码比较关注的就是 切点(pointcut) 和 通知 (advice)

下面我们开始撸代码!

  • 代码实现

如果对AOP编码不熟悉的同学,可以移步官方文档:

https://docs.spring.io/spring-framework/reference/core/aop.html

下面上我的切面类的代码

package com.cjt.demo.springaopdemo.aop;

import com.cjt.demo.springaopdemo.utils.JsonUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;

import java.util.UUID;

/*****************************************************
 * @package com.cjt.demo.springaopdemo.aop
 * @class WebParamAspect
 * @author caojiantao
 * @datetime 2024/6/21 14:53
 * @describe WEB请求参数打印切面类
 *           https://docs.spring.io/spring-framework/reference/core/aop.html
 ****************************************************/
@Aspect
@Component
@ConditionalOnClass(value = {ObjectMapper.class})
public class WebParamAspect {

    /**
     * 全局日志记录
     */
    private static final Logger PARAM_LOG = LoggerFactory.getLogger(WebParamAspect.class);


    /**
     * 定义切点: execution ,with,target等,可以参考官方文档
     * https://docs.spring.io/spring-framework/reference/core/aop/ataspectj/pointcuts.html
     */
    @Pointcut(value = "execution(public * com.cjt.demo.springaopdemo.controller..*(..))")
    public void cut() {
    }

    /**
     * 使用环绕通知:可以拿到请求前和请求后的参数,同时打印
     *
     * @param joinPoint 连接点
     * @return
     */
    @Around(value = "cut()")
    public Object printWebParam(ProceedingJoinPoint joinPoint) throws Throwable {
        // 可以加入接口记录时间,方法进入的时间
        long start = System.currentTimeMillis();

        // 获取连接点方法签名,可以从中解析得到关于该方法的许多信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        // 方法所在类名称
        String declaringTypeName = methodSignature.getDeclaringTypeName();
        // 方法名
        String methodName = methodSignature.getMethod().getName();

        // 生成唯一的交易编码,便于观察出参和入参
        String uuid = UUID.randomUUID().toString();

        // 获取参数
        Object[] args = joinPoint.getArgs();
        String jsonString = JsonUtil.toJSONString(args);
        // 判断是否开启打印,打印请求信息
        if (PARAM_LOG.isInfoEnabled()) {
            PARAM_LOG.info("TRACE:{} , 请求服务{} - 请求方法{}, 请求参数: {}", uuid, declaringTypeName, methodName, jsonString);
        }


        Object result = null;
        try {
            // 执行代理类的实现逻辑
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            if (PARAM_LOG.isErrorEnabled()) {
                PARAM_LOG.error("TRACE:{} , 请求服务{} - 请求方法{}, 发生异常了,原因是: {}", uuid, declaringTypeName, methodName, throwable.getMessage(), throwable);
            }
            throw throwable;
        }
        // 方法结束的时间
        long end = System.currentTimeMillis();
        // 判断是否开启打印,打印结果信息
        if (PARAM_LOG.isInfoEnabled()) {
            PARAM_LOG.info("TRACE:{} , 请求服务{} - 请求方法{} , 请求耗时 : {} ms , 请求结果: {}", uuid, declaringTypeName, methodName, (end - start), jsonString);
        }

        return result;
    }

}
  • 切点使用了 execution表达式,只拦截controller层,关于如何定义切点可以参考官方文档:

​​​​​​​Declaring a Pointcut :: Spring Framework

  • 通知类型使用了环绕通知,可以拿到代理方法执行前后的结果进行显示
  • 日志打印个性化的加入了UUID作为链路跟踪,可以很清晰的看入参和出参,同时在打印出差记录的时候又显示了接口执行的时间。
  • 演示效果

演示效果如下图所示:

可以看到成对的uuid可以明显的定位到一个请求的出参和入参情况,也能直观的看的请求的耗时,另外调用的类和方法也很明确的打印出来了,给后续的日志排查问题定位代码提供的便利,

  • 源码地址

笔者的demo代码使用的是 springboot单体架构

主要技术点: SpringBoot + Sqlite + knife4j + Mybatis 

https://github.com/1989Jiangtao/spring-aop-demo.giticon-default.png?t=N7T8https://github.com/1989Jiangtao/spring-aop-demo.git

Jiangtao/spring-aop-demoicon-default.png?t=N7T8https://gitee.com/caojiangtao1989/spring-aop-demo.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1848532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

svm和决策树基本知识以及模型评价以及模型保存

svm和决策树基本知识以及模型评价以及模型保存 文章目录 一、SVM1.1,常用属性函数 二、决策树2.1,常用属性函数2.2,决策树可视化2.3,决策树解释 3,模型评价3.1,方面一(评价指标)3.2&…

js浅拷贝和深拷贝的区别

JavaScript中的浅拷贝和深拷贝的主要区别在于它们如何处理引用类型的数据。 浅拷贝仅复制对象的引用,而不复制对象本身。这意味着新旧对象共享同一块内存空间。因此,如果修改了原始对象,复制的对象也会相应地改变,因为它们实际上是…

Webstorm vue项目@路径不能跳转到对应资源,提示Cannot find declaration to go to

Webstorm vue项目路径不能跳转到对应资源,提示Cannot find declaration to go to 我们 ctrl加鼠标左键点击方法会失效,看了网上很多教程在说需要在此处配置一下webpack.config.js的文件路径,而且指向了node_modules\vue\cli-service\webpack.config.js 我…

网络安全:Web 安全 面试题.(SQL注入)

网络安全:Web 安全 面试题.(SQL注入) 网络安全面试是指在招聘过程中,面试官会针对应聘者的网络安全相关知识和技能进行评估和考察。这种面试通常包括以下几个方面: (1)基础知识:包括网络基础知识、操作系…

基于PSO粒子群优化的CNN-GRU的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 卷积神经网络(CNN) 4.2 CNN-GRU模型架构 4.3 CNN-GRU结合PSO的时间序列预测 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软…

Maven笔记(更新中)

一、Maven简介 Maven是一款为Java项目构建,依赖管理的工具(软件),使用Maven可以自动化构建,测试,打包和发布项目,大大提高了开发效率和质量 Maven主要作用理解 依赖管理 Maven可以管理项目的依赖,包括自动下载所需依赖库,自动下载依赖所需的依赖并且保证版本没有冲突,依赖版…

小米红米手机刷Hyper澎湃OS欧版EU教程-全球语言-完整GO框架-纯净飞速

有很多小伙伴喜欢刷小米欧版EU系统,EU版本由于很多base_china,自然稳定性来说,相对于别的区域来说,稳定真的太多,不会出现信号或者相机等奇奇怪怪的BUG,这也是我 们将欧版EU作为第一选择的原因。从界面来看…

OpenHarmony-HDF驱动框架介绍及加载过程分析

前言 HarmonyOS面向万物互联时代,而万物互联涉及到了大量的硬件设备,这些硬件的离散度很高,它们的性能差异与配置差异都很大,所以这要求使用一个更灵活、功能更强大、能耗更低的驱动框架。OpenHarmony系统HDF驱动框架采用C语言面…

综合评价 | 基于因子分析和聚类分析的节点重要度综合评价(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 综合评价 | 基于因子分析和聚类分析的节点重要度综合评价(Matlab) 程序设计 完整程序和数据获取方式:私信博主回复基于因子分析和聚类分析的节点重要度综合评价(Matlab…

张量 Tensor学习总结

张量 Tensor 张量是一种多线性函数,用于表示矢量、标量和其他张量之间的线性关系,其在n维空间内有n^r个分量,每个分量都是坐标的函数。张量在坐标变换时也会按照某些规则作线性变换,是一种特殊的数据结构,在MindSpore…

【odoo】常用的基本视图类型

概要 在Odoo中,有几种基本视图类型,每种视图类型用于不同的目的和场景。这些视图类型包括表单视图(form view)、树视图(tree view)、看板视图(kanban view)、图表视图(gr…

海口注册公司代理记账的服务优势与流程解析

在海口注册公司加入代理记账服务有多种优势。代理记账公司提供专业的财务服务,帮助企业节约成本、提高效率,实现财务管理的合规性。以下是代理记账服务的主要优势和流程解析: https://www.9733.cn/news/detail/173.html 一、代理记账服务的…

Python酷库之旅-第三方库openpyxl(02)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

时序预测 | Matlab基于CNN-BiLSTM-Attention多变量时间序列多步预测

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于CNN-BiLSTM-Attention多变量时间序列多步预测; 2.多变量时间序列数据集(负荷数据集),采用前96个时刻预测的特征和负荷数据预测未来96个时刻的负荷数据&…

make与makefile

目录 一、make的默认目标文件与自动推导 二、不能连续make的原因 执行原理 touch .PHONY伪目标 make指令不回显 makefile多文件管理 简写依赖方法 三、回车与换行 四、缓冲区 一、make的默认目标文件与自动推导 假设这是一个makefile文件,make的时候默认生…

百度文心智能体,创建属于自己的智能体应用

百度文心智能体平台为你开启。百度文心智能体平台,创建属于自己的智能体应用。百度文心智能体平台是百度旗下的智能AI平台,集成了先进的自然语言处理技术和人工智能技术,可以用来创建属于自己的智能体应用,访问官网链接&#xff1…

【地质灾害监测实现有效预警,44人提前安全转移】

6月13日14时,国信华源地质灾害监测预警系统提前精准预警,安全转移10户44人。 该滑坡隐患点通过科学部署国信华源裂缝计、倾角加速度计、雨量计、预警广播等自动化、智能化监测预警设备,实现了对隐患点裂缝、位移、降雨量等关键要素的实时动态…

嵌入式Linux驱动开研发流程详细解析

大家好,今天主要给大家分享一下,嵌入式linux中重要的内容详解。 一、驱动概念 驱动与底层硬件直接打交道,充当了硬件与应用软件中间的桥梁。 具体任务 读写设备寄存器(实现控制的方式) 完成设备的轮询、中断处理、DMA通信(CPU与外设通信的方式) 进行物理内存向虚拟内存…

nodejs执行 npm run dev时错误

INFO Starting development server… 95% emitting CompressionPlugin ERROR Error: error:0308010C:digital envelope routines::unsupported 我的node.js 的版本是 node-v20.11.0-x64 ,听说16以上的版本会有这个问题,具体是什么忘了。需要在npm run …

【背包题解】DP代表了走到阶段i 的所有路线的最优解

1889:【提高】多重背包(2) 二维费用背包 2075 - 最大卡路里 1928 - 采购礼品 感谢 背包容量:(c) 6 重量 weight 2 2 4 6 2 1 2 3 4 5 价值 value 3 6 5 5 8 1 2 3 4 5 wvdp数组:记录有i件…