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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值