JAVA @interface自定义注解(自定义注解+环绕通知 记录操作日志)

简介

注解@interface是一种在Java代码中添加元数据(metadata)的方式,它可以用于提供程序的额外信息,但本身并不会直接影响程序的执行。注解可以应用于类、方法、字段和其他程序元素,用于提供关于这些元素的额外信息。
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。
在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

自定义注解的应用场景

自定义注解注释在方法上,给方法提供元数据,一般与过滤器,拦截器,SpringAOP配置使用,这些系统自动调用的功能,可以获取自定义元数据的信息做相应的处理。也可以程序在调用的方法中,使用注解的元信息,例如阿里提供的easyExcel,在需要输出excel的数据的类上添加注解提供列名,数据格式等信息,在客户化方法调用生成excel获取这些元数据形成表格。

  • 1.记录操作日志
  • 2.权限校验
  • 3.参数校验
  • 4.easy excel
  • 5.事务管理
  • 6.数据源切换

元注解(可以注解在自定义注解上的注解)

  • @Target:用于指定注解可以应用于的目标元素类型,限定注解可以放在那些对象上

    • ElementType.TYPE: 类、接口或枚举类型
    • ElementType.FIELD: 字段(包括枚举常量)
    • ElementType.METHOD: 方法
    • ElementType.PARAMETER: 方法或构造函数的参数
    • ElementType.CONSTRUCTOR: 构造函数
    • ElementType.LOCAL_VARIABLE: 局部变量
    • ElementType.ANNOTATION_TYPE: 注解类型
    • ElementType.PACKAGE: 包
    • ElementType.TYPE_PARAMETER: 类型参数(Java 8+)
    • ElementType.TYPE_USE: 类型使用(Java 8+)
  • @Retention:用于指定注解的保留策略(retention policy)。保留策略定义了注解在何时有效以及在何时丢弃。

    • RetentionPolicy.SOURCE: 这是最短的保留策略。注解仅保留在源代码中,不会被编译进 class 文件,也不会对运行时产生任何影响。一般用于生成文档等需要在源代码中保留信息的情况。
    • RetentionPolicy.CLASS: 注解会被编译进 class 文件中,但在运行时不会被 JVM 保留。这是默认的保留策略,如果不指定 @Retention,则会使用这种策略。
    • RetentionPolicy.RUNTIME: 注解会被编译进 class 文件中,并在运行时被 JVM 保留,可以通过反射机制获取注解信息。这种保留策略通常用于实现自定义的注解处理器,或者需要在运行时获取注解信息的情况。
  • @Inherited:表示:父类有这个注解,子类继承父类,会一并继承这个注解。

  • @Documented:API 文档中,而不影响注解应用的元素是否包含在文档中。

案例:自定义注解+环绕通知 记录操作日志

要求:知道操作类型(type):增删改查,操作说明(action):具体做什么,方法入参,出参数,方法名,执行结果。

定义表:

CREATE TABLE learn.user_operation_log (
    id bigint  ,
    type VARCHAR(30) NOT NULL,
    action VARCHAR(240) NOT NULL,
    method_name VARCHAR(80) NOT NULL,
    method_params TEXT,
    result_code VARCHAR(240),
    result_msg VARCHAR(240) ,
    create_by long NOT NULL,
    update_by long NOT NULL,
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

定义DAO


@Data
@TableName("user_operation_log")
public class OperationLogDO extends BaseDO {

    private String type;

    private String action;

    private String methodName;

    private String methodParams;

    private String resultCode;

    private String resultMsg;
}

这里面的ID字段使用mybatis自动填充,也可以手工赋值

@Data
public class BaseDO implements Serializable {

    /**
     *
     */
    // 雪花算法:一位未定 + 41位时间数字 + 10位机器数(5位机器时区+5位机器编码)+ 12位随机数
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /**
     *
     */
    @TableField(fill = FieldFill.INSERT)
    private Long createBy;

    /**
     *
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateBy;

    /**
     *
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     *
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

Mapper和持久化Service省略

定义自定义注解

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AuditLog {
    // 1.操作描述
    String action() default "";

    // 2.操作类型(增删改查)
    OperateEnum type() default OperateEnum.MODIFY;

    // 3.是否记录参数
    boolean isRecord() default true;
}

定义切面:环绕通知

@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class AuditAspect {
    private final ObjectMapper objectMapper;

    private final OperationLogService operationLogService; 

    @Around("@annotation(auditLog)")
    public Object around(ProceedingJoinPoint joinPoint, AuditLog auditLog) throws Throwable {
        log.info("开始记录日志");
        // 执行连接点前,记录信息
        OperationLogDO userLogRecordDO = buildRequestParams(joinPoint, auditLog);

        try {
            Object result = joinPoint.proceed();
            if (result != null) {
                String resultJson = objectMapper.writeValueAsString(result);
                userLogRecordDO.setResultCode("S");
                userLogRecordDO.setResultMsg(resultJson);
            }
            return result;
        } catch (Exception e) {
            userLogRecordDO.setResultCode("E");
            userLogRecordDO.setResultMsg("后端未知异常,错误消息:" + e.getMessage());
            throw e;
        } finally {
            //操作日志入库
            try {
                if (userLogRecordDO.getId() != null) {
                    operationLogService.updateById(userLogRecordDO);
                }
            } catch (Exception e) {
                log.error("更新用户操作异常", e);
            }
            log.info("结束记录日志");
        }
    }

    private OperationLogDO buildRequestParams(ProceedingJoinPoint joinPoint, AuditLog auditLog) {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // AuditLog auditLog = method.getAnnotation(AuditLog.class);

        OperationLogDO operationLogDO = new OperationLogDO();
        operationLogDO.setAction(auditLog.action());
        operationLogDO.setType(auditLog.type().name());
        // 类名+方法名
        operationLogDO.setMethodName(method.getDeclaringClass().getSimpleName() + "." + method.getName());

        // 处理入参数
        try {
            Parameter[] parameters = methodSignature.getMethod().getParameters();
            HashMap<String, Object> paramMap = new HashMap<>();
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < parameters.length; i++) {
//            Class<?> type = parameters[i].getType();
//            if (ServletResponse.class.isAssignableFrom(type) || ServletRequest.class.isAssignableFrom(type)) {
//                continue;
//            }
                String name = parameters[i].getName();
                paramMap.put(name, args[i]);
                operationLogDO.setMethodParams(objectMapper.writeValueAsString(paramMap));
            }
        } catch (JsonProcessingException e) {
            log.warn("构建入参异常:{}", e.getMessage());
        }
        //操作日志入库
        try {
            operationLogService.save(operationLogDO);
        } catch (Exception e) {
            log.error("记录用户操作异常:", e);
        }

        return operationLogDO;
    }
}

操作类型常量

public enum OperateEnum {
    ADD,
    DELETE,
    MODIFY,
    SAVE_OR_MODIFY,
    SELECT;
}

方法上添加自定义注解

@Slf4j
@Service
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {

    private final StudentRepository studentRepository;


    @Override
    // @MetricTime("createStudent") // 切面与注释配套使用
    @AuditLog(action = "创建学生", type = OperateEnum.ADD, isRecord = true)
    public StudentResult createStudent(StudentCreateCmd cmd) {
        log.info("create student cmd:{}", cmd);
        StudentDO studentDO = StudentCreateCmd.convertToDO(cmd);
        studentRepository.save(studentDO);
        log.info("create student end");
        return StudentResult.convertFromDO(studentDO);
    }
}

运行测试结果
在这里插入图片描述

参考网址:

https://blog.csdn.net/m0_52767002/article/details/133793285

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理 运算器

运算方法和运算器&#xff08;重点&#xff09; B二进制(binary), D十进制(decimal), H十六进制(hexadecimal) 纯小数和纯整数表示范围 设机器字长n1位&#xff0c;规定最高位&#xff08;第n1位&#xff09;为符号位 纯小数最大范围中的可理解为小数部分全为0的“1”&#…

TCP连接三次握手的过程,为什么是三次,可以是两次或者更多吗?

(1) 三次握手的过程 第一次握手&#xff1a;客户端向服务器发送一个包含SYN &#xff08;同步序列编号&#xff09;和初始序列号&#xff08;ISN&#xff09;的报文&#xff0c;请求建立连接&#xff0c;客户端进入SYN_SENT (同步已发送)状态。第二次握手&#xff1a;服务器收…

Python酷库之旅-第三方库Pandas(027)

目录 ​一、用法精讲 68、pandas.infer_freq函数 68-1、语法 68-2、参数 68-3、功能 68-4、返回值 68-5、说明 68-6、用法 68-6-1、数据准备 68-6-2、代码示例 68-6-3、结果输出 69、pandas.interval_range函数 69-1、语法 69-2、参数 69-3、功能 69-4、返回值…

Open3D Ransac拟合空间直线

目录 一、概述 1.1实现步骤 1.2优势与局限 二、代码实现 2.1关键代码 2.2完整代码 三、实现效果 前期试读&#xff0c;后续会将博客加入该专栏&#xff0c;欢迎订阅 Open3D点云算法与点云深度学习案例汇总&#xff08;长期更新&#xff09;-CSDN博客 一、概述 RANSAC&…

VScode终端和外部终端中文乱码问题

VScode终端和外部终端中文乱码问题 前言VScode终端VScode的第二大特点方法一方法二外部终端&#xff08;命令为ctrlf5&#xff09; 总结实现VScode终端和外部终端都能运行可执行文件 心得 前言 如果只想要看解决方案可直接跳转到总结部分&#xff0c;其余的章节只是用来说明原…

解决C#读取US7ASCII字符集oracle数据库的中文乱码

&#x1f468; 作者简介&#xff1a;大家好&#xff0c;我是Taro&#xff0c;全栈领域创作者 ✒️ 个人主页&#xff1a;唐璜Taro &#x1f680; 支持我&#xff1a;点赞&#x1f44d;&#x1f4dd; 评论 ⭐️收藏 文章目录 前言一、解决方法二、安装System.Data.OleDb连接库三…

第7章 模块(2)

目录 7.3 插入和删除模块 7.3.1 模块的表示 7.3.2 依赖关系和引用 7.3.3 模块的二进制结构 7.3.4 插入模块 7.3.5 移除模块 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;查看后续文章。 7.3 插入和删除模块 两个系统调用&#xff1a; init_module&#xff1…

考研数学二战,怎么准备才能提升大?

一战70多...二战提升空间那是相当的大 我身边很多一战甚至不到60&#xff0c;二战成绩飙到120的&#xff0c;真的很猛 所以你根本不用担心是自己学数学没天赋&#xff0c;其实知识方法没用对而已 本人属于基础很差相当于是零基础的23考研党&#xff0c;经过一年备考成功上岸…

k8s集群 安装配置 Prometheus+grafana+alertmanager

k8s集群 安装配置 Prometheusgrafanaalertmanager k8s环境如下&#xff1a;机器规划&#xff1a; node-exporter组件安装和配置安装node-exporter通过node-exporter采集数据显示192.168.40.180主机cpu的使用情况显示192.168.40.180主机负载使用情况 Prometheus server安装和配置…

JayChou周杰伦的歌曲网易云音乐怎么听

听Jay自由 网易云导入 专辑介绍 周杰伦&#xff08;Jay Chou&#xff09;是一位著名的台湾流行歌手、词曲创作人和演员。他以其独特的音乐风格和才华横溢的创作能力而闻名于世。以下是对周杰伦所有专辑的简要介绍&#xff1a; 《Jay》&#xff08;2000年&#xff09;&#xf…

独立开发者系列(26)——域名与解析

域名&#xff08;英语&#xff1a;Domain Name&#xff09;&#xff0c;又称网域&#xff0c;是由一串用点分隔的名字组成的互联网上某一台计算机或计算机组的名称&#xff0c;用于在数据传输时对计算机的定位标识&#xff08;有时也指地理位置&#xff09;。 由于IP地址不方便…

Leaflet集成wheelnav在WebGIS中的应用

目录 前言 一、两种错误的实现方式 1、组件不展示 2、意外中的空白 二、不同样式的集成 1、在leaflet中集成wheelnav 2、给marker绑定默认组件 2、面对象绑定组件 3、如何自定义样式 三、总结 前言 在之前的博客中&#xff0c;我们曾经介绍了使用wheelnav.js构建酷炫…

Flink底层原理解析:案例解析(第37天)

系列文章目录 一、flink架构 二、Flink底层原理解析 三、Flink应用场景解析 四、fink入门案例解析 文章目录 系列文章目录前言一、flink架构1. 作业管理器&#xff08;JobManager&#xff09;2. 资源管理器&#xff08;ResourceManager&#xff09;3. 任务管理器&#xff08;Ta…

【八股系列】CSS盒模型:掌握网页布局的核心

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【Vue中的&#xff1c;keep-alive&#xff1e;组件&#xff1a;深入解析与实践指南】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点赞&…

夏日狂欢水上漂流的爆笑奇遇记

【夏日狂欢&#xff0c;水上漂流的爆笑奇遇记 —— 月亮姐姐的“睫毛漂流记”】在这个炎炎夏日&#xff0c;当烈日炙烤着大地&#xff0c;每一寸空气弥漫着对清凉的渴望时&#xff0c;一场别开生面的“暑期嘉年华”正悄然掀起一场水上狂欢的浪潮。而在这场盛宴中&#xff0c;月…

FPGA实训报告DAY 1(Verilog HDL)

实习日志与总结 日期&#xff1a;2024 年 7 月 10 日 星期三 姓名&#xff1a;XXX 一、实习日志 上午 9:00 - 9:30 按时到达工位&#xff0c;参加部门早会&#xff0c;了解了今天的实习任务和目标&#xff0c;即初步学习 FPGA 简介和 Verilog 基础语法知识。 9:30 - 10:30…

springboot 集成minio,启动报错

springboot 集成 minio 8.5.10 报错 *************************** APPLICATION FAILED TO START *************************** Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: io.minio.S3Base.…

在mybatis-plus中关于@insert注解自定义批处理sql导致其雪花算法失效而无法自动生成id的解决方法

受到这位作者的启发 > 原文在点这里 为了自己实现批量插入&#xff0c;我在mapper层使用insert注解写了一段自定义sql //自定义的批量插入方法 Insert("<script>" "insert into rpt_material_hour(id,sample_time,rounding_time,cur_month,machine_no…

启智畅想火车类集装箱号码识别技术,软硬件解决方案

集装箱号码识别需求&#xff1a; 实时检测车皮号、火车底盘号码、集装箱号码&#xff0c;根据火车类型分为以下三种情况&#xff1a; 1、纯车皮&#xff0c;只检测车皮号&#xff1b; 2、火车拉货箱&#xff08;半车皮&#xff09;&#xff0c;检测车皮号集装箱号码&#xff1b…

基于springboot和mybatis的RealWorld后端项目实战一之hello-springboot

新建Maven项目 注意archetype选择quickstart pom.xml 修改App.java App.java同级目录新增controller包 HelloController.java package org.example.controller;import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotatio…