使用AOP实现自定义日志

在实现自定义日志之前,我们需要了解AOP。

1.AOP

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从主要业务逻辑中分离出来,使得代码更模块化、更易于维护。横切关注点指的是那些存在于应用程序中多个模块中、并且对整个应用程序产生影响的功能,比如日志记录、事务管理、安全性等。

AOP的核心概念包括:切面(Aspect): 切面是一组横切关注点的模块化单元,它定义了在何处(切点)、何时(通知)以及如何执行横切关注点。连接点(Join Point): 连接点是在应用程序执行过程中能够应用切面的点,比如方法调用、异常处理等。切点(Pointcut): 切点是一组连接点的集合,用于定义在何处应用切面。通常,切点通过表达式或者正则表达式定义。通知(Advice): 通知是切面在连接点上执行的动作,它定义了在连接点的何时执行什么操作,比如在方法执行前后、抛出异常时执行的操作。引入(Introduction): 引入允许我们向现有的类添加新的方法或属性,而不需要修改它们的源代码。目标对象(Target Object): 目标对象是一个应用程序类,它可能包含切点,也可能不包含。织入(Weaving): 织入是将切面应用到目标对象并创建新的代理对象的过程。织入可以在编译时、类加载时、运行时进行。

AOP主要用于解决横切关注点的问题,这些关注点通常涉及多个模块并且难以在代码中统一管理。通过使用AOP,可以使得这些关注点更加集中和可维护,提高了代码的可读性和可维护性。常见的AOP框架包括Spring AOP、AspectJ等。

2.实现

**

第一步:需要在pom.xml文件中引入AOP 的依赖

**

		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
第二步:自定义日志注解
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*** 模块*/public String title() default "";/*** 功能*/public BusinessType businessType() default BusinessType.OTHER;/*** 操作人类别*/public OperatorType operatorType() default OperatorType.MANAGE;/*** 是否保存请求的参数*/public boolean isSaveRequestData() default true;/*** 是否保存响应的参数*/public boolean isSaveResponseData() default true;
}

解释一下这里的注解的意思,没记错的话应该是java8新特性(也可能记错了)里的东西,想深入了解的可以去看看。

@Target 用于指定注解的作用范围,即注解可以被应用在哪些元素上。
在提供的例子中,@Target({ElementType.PARAMETER, ElementType.METHOD})
表示这个注解可以应用在方法的参数和方法上。

@Retention 用于指定注解的保留策略,即注解在何时生效。 RetentionPolicy.RUNTIME
表示注解会在运行时保留,因此可以通过反射机制来获取注解信息。

@Documented 用于指定注解是否应该被 javadoc 工具记录。 如果一个注解被 @Documented 标记,那么它会被
javadoc 工具提取并文档化,这使得该注解的信息可以包含在生成的文档中。

这三个元注解的使用通常是一起的,它们提供了对注解的定义、应用范围和保留策略的灵活控制。在提供的例子中,这个注解的含义是可以应用在方法参数和方法上,在运行时保留,并且会被 javadoc 工具文档化。

第三步,构建你的操作类型,或者操作人(使用枚举)
* 业务操作类型**/
public enum BusinessType {/*** 其它*/OTHER,/*** 新增*/INSERT,/*** 修改*/UPDATE,/*** 删除*/DELETE,/*** 授权*/ASSGIN,/*** 导出*/EXPORT,/*** 导入*/IMPORT,/*** 强退*/FORCE,/*** 更新状态*/STATUS,/*** 清空数据*/CLEAN,/*** 批量删除*/BATCHDELETE,
}
* 操作人类别**/
public enum OperatorType {/*** 其它*/OTHER,/*** 后台用户*/MANAGE,/*** 手机端用户*/MOBILE
}
第四步,

在这里我们要建立业务层,用来将生产日志保存在数据库中

public interface AsyncOperLogService {public void savaOperLog(BhOperLog bhOperLog);
}

通过实现类完成具体插入代码,这里使用mybatisplus,不了解的可以看我之前的笔记MyBatisPlus

@Overridepublic void savaOperLog(BhOperLog bhOperLog) {bhOperLogMapper.insert(bhOperLog);}

下面是具体的实体类

@Data
@ApiModel(description = "系统操作日志")
@TableName("你的表名称")
public class OperLog extends BaseEntity {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "id")private long id;@ApiModelProperty(value = "模块标题")@TableField("title")private String title;@ApiModelProperty(value = "业务类型(0其它 1新增 2修改 3删除)")@TableField("business_type")private String businessType;@ApiModelProperty(value = "方法名称")@TableField("method")private String method;@ApiModelProperty(value = "请求方式")@TableField("request_method")private String requestMethod;@ApiModelProperty(value = "操作类别(0其它 1后台用户 2手机端用户)")@TableField("operator_type")private String operatorType;@ApiModelProperty(value = "操作人员")@TableField("oper_name")private String operName;@ApiModelProperty(value = "部门名称")@TableField("dept_name")private String deptName;@ApiModelProperty(value = "请求URL")@TableField("oper_url")private String operUrl;@ApiModelProperty(value = "主机地址")@TableField("oper_ip")private String operIp;@ApiModelProperty(value = "请求参数")@TableField("oper_param")private String operParam;@ApiModelProperty(value = "返回参数")@TableField("json_result")private String jsonResult;@ApiModelProperty(value = "操作状态(0正常 1异常)")@TableField("status")private Integer status;@ApiModelProperty(value = "错误消息")@TableField("error_msg")private String errorMsg;@ApiModelProperty(value = "操作时间")@TableField("oper_time")private Date operTime;
}
第五步,完成具体的日志配置开发

下面是完整代码

* 操作日志记录处理**/
@Aspect
@Component
public class LogAspect {@ResourceAsyncOperLogService asyncOperLogService;/*** 处理完请求后执行** @param joinPoint 切点*/@AfterReturning(pointcut = "@annotation(controllerLog)",returning = "jsonResult")public void doAfterReturnin(JoinPoint joinPoint, Log controllerLog, Object jsonResult){handleLog(joinPoint, controllerLog,null, jsonResult);}protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {try {RequestAttributes ra = RequestContextHolder.getRequestAttributes();ServletRequestAttributes sra = (ServletRequestAttributes) ra;HttpServletRequest request = sra.getRequest();// *========数据库日志=========*//BhOperLog operLog = new BhOperLog();operLog.setStatus(1);// 请求的地址 IpUtil.getIpAddr(ServletUtils.getRequest());String ip = IpUtil.getIpAddress(request);operLog.setOperIp(ip);operLog.setOperUrl(request.getRequestURI());String token = request.getHeader("token");String userName = JwtUtils.getUsername(token);operLog.setOperName(userName);if (e != null) {operLog.setStatus(0);operLog.setErrorMsg(e.getMessage());}// 设置方法名称String className = joinPoint.getTarget().getClass().getName();String methodName = joinPoint.getSignature().getName();operLog.setMethod(className + "." + methodName + "()");// 设置请求方式operLog.setRequestMethod(request.getMethod());// 处理设置注解上的参数getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);// 保存数据库asyncOperLogService.savaOperLog(operLog);} catch (Exception exp) {// 记录本地异常日志
//            log.error("==前置通知异常==");
//            log.error("异常信息:{}", exp.getMessage());exp.printStackTrace();}}/*** 获取注解中对方法的描述信息 用于Controller层注解** @param log     日志* @param operLog 操作日志* @throws Exception*/public void getControllerMethodDescription(JoinPoint joinPoint, Log log, BhOperLog operLog, Object jsonResult) throws Exception {// 设置action动作operLog.setBusinessType(log.businessType().name());// 设置标题operLog.setTitle(log.title());// 设置操作人类别operLog.setOperatorType(log.operatorType().name());// 是否需要保存request,参数和值if (log.isSaveRequestData()) {// 获取参数的信息,传入到数据库中。setRequestValue(joinPoint, operLog);}// 是否需要保存response,参数和值if (log.isSaveResponseData() && !StringUtils.isEmpty(jsonResult)) {operLog.setJsonResult(JacksonUtils.obj2String(jsonResult));}}/*** 获取请求的参数,放到log中** @param operLog 操作日志* @throws Exception 异常*/private void setRequestValue(JoinPoint joinPoint, BhOperLog operLog) throws Exception {String requestMethod = operLog.getRequestMethod();if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {String params = argsArrayToString(joinPoint.getArgs());operLog.setOperParam(params);}}/*** 参数拼装*/private String argsArrayToString(Object[] paramsArray) {String params = "";if (paramsArray != null && paramsArray.length > 0) {for (Object o : paramsArray) {if (!StringUtils.isEmpty(o) && !isFilterObject(o)) {try {String jsonObj = JacksonUtils.obj2String(o);params += jsonObj + " ";} catch (Exception e) {}}}}return params.trim();}/*** 判断是否需要过滤的对象。** @param o 对象信息。* @return 如果是需要过滤的对象,则返回true;否则返回false。*/@SuppressWarnings("rawtypes")public boolean isFilterObject(final Object o) {Class<?> clazz = o.getClass();if (clazz.isArray()) {return clazz.getComponentType().isAssignableFrom(MultipartFile.class);} else if (Collection.class.isAssignableFrom(clazz)) {Collection collection = (Collection) o;for (Object value : collection) {return value instanceof MultipartFile;}} else if (Map.class.isAssignableFrom(clazz)) {Map map = (Map) o;for (Object value : map.entrySet()) {Map.Entry entry = (Map.Entry) value;return entry.getValue() instanceof MultipartFile;}}return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse|| o instanceof BindingResult;}}

那么如何使用呢?

只需要在你需要的Controller中加入
这个注解就可以了
@Log(title = "菜单管理", businessType = BusinessType.STATUS) 其中title根据具体模块书写,businessType就是你自定义的操作类型

这样就完成了自定义日志的开发,也可以将这些代码通过自定义start进行打包,加入Maven中进行使用。

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

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

相关文章

macOS跨进程通信: XPC 创建实例

一&#xff1a;简介 XPC 是 macOS 里苹果官方比较推荐和安全的的进程间通信机制。 集成流程简单&#xff0c;但是比较绕。 主要需要集成 XPC Server 这个模块&#xff0c;这个模块最终会被 apple 的根进程 launchd 管理和以独立进程的方法唤起和关闭&#xff0c; 我们主app 进…

【运维】界面操作与直接操作

对于一个大型的复杂的系统&#xff0c;各个模块之间紧密关联&#xff0c;界面上的一个操作&#xff0c;可能涉及到很多底层逻辑的连携反应。 在一个共享文件系统中&#xff0c;一个简单的小操作&#xff0c;可能就涉及到数据库多张表的改动&#xff0c;而且往往改动的表并不是…

Flume基础知识(四):Flume实战之实时监控单个追加文件

1&#xff09;案例需求&#xff1a; 实时监控 Hive 日志&#xff0c;并上传到 HDFS 中 2&#xff09;需求分析&#xff1a; 3&#xff09;实现步骤&#xff1a; &#xff08;1&#xff09;Flume 要想将数据输出到 HDFS&#xff0c;依赖 Hadoop 相关 jar 包 检查/etc/profile.d…

【MySQL】MySQL运维SQL(持续更新。。。)

MySQL运维SQL 信息统计 查询数据库有多少张表 SELECT COUNT(*) AS table_count FROM information_schema.TABLES WHERE table_schema 数据库名;查询表中有多少个字段 SELECTCOUNT(*) AS field_count FROMinformation_schema.COLUMNS WHEREtable_schema 数据库名 AND tab…

Spark内核解析-Spark shuffle6(六)

1、Spark Shuffle过程 1.1MapReduce的Shuffle过程介绍 Shuffle的本义是洗牌、混洗&#xff0c;把一组有一定规则的数据尽量转换成一组无规则的数据&#xff0c;越随机越好。MapReduce中的Shuffle更像是洗牌的逆过程&#xff0c;把一组无规则的数据尽量转换成一组具有一定规则…

RFID数据中心智能资产管理系统

数据中心机房承担着保障企业关键数据处理的重要责任&#xff0c;机房的日常管理直接关系到整体机房的日常维护和运行安全&#xff0c;数据资产管理中心在监管机房各部分设备的运行情况、维护数据中心的资产方面发挥着重要的作用。 成功的数据中心机房管理不仅需要选择高可靠性…

游戏分组(100用例)C卷 (JavaPythonC语言C++Node.js)

部门准备举办一场王者荣耀表演赛,有10名游戏爱好者参与,分为两队,每队5人。 每位参与者都有一个评分,代表着他的游戏水平。为了表演赛尽可能精彩,我们需要把10名参赛者分为实力尽量相近的两队。一队的实力可以表示为这一队5名队员的评分总和。 现在给你10名参与者的游戏水…

NFT 项目入驻 NFTScan Site 流程说明

NFTScan Site 是由数据基础设施 NFTScan 推出的功能强大的 NFT 项目管理平台。NFTScan Site 主要为 NFT Collection、NFT Marketplace、NFTFi 以及其他 NFT 生态项目提供专业的项目管理后台服务和链上数据分析追踪服务。 NFTScan Site 功能&#xff1a; 1&#xff09;项目信息编…

鸿蒙开发之Touch事件拦截stopPropagation()

在读Touch事件官方文档的时候&#xff0c;遇到了一个属性 其他属性都好理解&#xff0c;这个阻塞事件冒泡什么意思呢&#xff1f;官网也没有解释 后来查资料知道这个方法是阻止onTouch冒泡传递到父组件。 show code Entry Component struct OfficialTouchPage {State messa…

【leetcode100-30】【链表】两两交换链表节点

【题干】 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 【思路】 先说递归的&#xff0c;退出条件很明显&#xff0c;当剩…

键盘符号中英文名称大全

符号中文英文~波浪号tilde反引号,重音号backquote,backtick,grave accent#井号number sign,pound sign,hash mark#!sha-bang|竖线pipe,vertical bar单引号single quote"双引号double quote,quotation mark$美元符号dollar sign.点,句号dot,period,point!感叹号exclamation…

C Primer Plus (中文版)第12章编程练习 参考答案(仅供参考~)

C Primer Plus &#xff08;中文版&#xff09;第12章编程练习 参考答案(仅供参考~) &#x1f334; C Primer Plus第12章编程练习~ 加油加油&#xff01;&#x1f36d; &#x1f36d;这一章主要是各种存储变量的应用 同时也有多文件应用的题~ &#x1f308;加油&#xff0c;我们…

Java项目:108SSM教务管理系统

博主主页&#xff1a;Java旅途 简介&#xff1a;分享计算机知识、学习路线、系统源码及教程 文末获取源码 一、项目介绍 教务管理系统基于SpringSpringMVCMybatis开发&#xff0c;功能和学生成绩管理系统&#xff0c;学生选课管理系统类似&#xff0c;也可以做学生成绩管理系统…

R语言【base】——do.call():根据名称或函数以及要传递给它的参数列表构造并执行函数调用

Package base version 4.2.0 Parameters do.call(what, args, quote FALSE, envir parent.frame()) 参数【what】&#xff1a;命名要调用的函数的函数或非空字符串。 参数【args】&#xff1a;函数调用的参数列表。参数【args】的 names 属性为参数提供名称。 参数【quot…

C# .Net学习笔记—— 异步和多线程(Task)

一、概念 Task是DotNet3.0之后所推出的一种新的使用多线程的方式&#xff0c;它是基于ThreadPool线程进行封装的。 二、使用多线程的时机 任务能够并发运行的时候&#xff0c;提升速度&#xff1b;优化体验 三、基本使用方法 private void button5_Click(object sender, Ev…

Mysql 动态链接库配置步骤+ 完成封装init和close接口

1、创建新项目 动态链接库dll 2、将附带的文件都删除&#xff0c;创建LXMysql.cpp 3、项目设置 3.1、预编译头&#xff0c;不使用预编译头 3.2、添加头文件 3.3、添加类 3.4、写初始化函数 4、项目配置 4.1、右键解决方案-属性-常规-输出目录 ..\..\bin 4.2、生成lib文件 右…

Android权限申请

目录 1. 静态申请 2. 动态申请 3. 判断权限申请情况 4. 确认权限 在android中所有的权限都需要静态申请&#xff0c;涉及用户隐私的高级权限还需要进行动态申请。 1. 静态申请 在AndroidManifest清单文件中进行申请。 <?xml version"1.0" encoding"u…

Debian部署Mysql8.0

1、下载对应系统的mysql-apt-config包 wget https://repo.mysql.com/apt/debian/pool/mysql-apt-config/m/mysql-apt-config/mysql-apt-config_0.8.29-1_all.deb2、安装mysql-apt-config包 # 安装下载的deb包 sudo dpkg -i mysql-apt-config_0.8.29-1_all.deb上下键选择菜单中…

Gateway相关面试题及答案(2024)

1、什么是API Gateway&#xff1f;它在微服务架构中有什么作用&#xff1f; API Gateway是一个服务器&#xff0c;通常是一个可以管理和处理所有进出应用程序的请求的反向代理。在微服务架构中&#xff0c;它作为单一的入口点&#xff0c;统一接收来自客户端的调用请求&#x…

Go语言常用类型互转方法归纳

前言 前文提到&#xff0c;Go基本数据类型有如下这些 布尔&#xff1a;bool 字符串&#xff1a;string 整数&#xff1a; int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 字节&#xff1a;byte &#xff0c;uint8 的别名 Unicode&#xff1a;rune &#xff0c;i…