自定义注解

Posted blwy-zmh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义注解相关的知识,希望对你有一定的参考价值。

基础知识:自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

定义注解类型元素时需要注意如下几点

  • 访问修饰符必须为public,不写默认为public;
  • 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
  • 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
  • ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
  • default代表默认值,值必须和第2点定义的类型一致;
  • 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

 常用的元注解

java中有四种元注解:@Retention、@Inherited、@Documented、@Target

@Retention 注解的保留位置(枚举RetentionPolicy),RetentionPolicy可选值:

  • SOURCE:注解仅存在于源码中,在class字节码文件中不包含(如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到)
  • CLASS:默认的保留策略,注解在class字节码文件中存在,但运行时无法获得(如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到)
  • RUNTIME:注解在class字节码文件中存在,在运行时可以通过反射获取到(如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME,在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS)

@Inherited 声明子类可以继承此注解,如果一个类A使用此注解,则类A的子类也继承此注解

@Documented 声明注解能够被javadoc等识别

@Target 用来声明注解范围(枚举ElementType),ElementType可选值:

  • TYPE:接口、类、枚举、注解
  • FIELD:字段、枚举的常量
  • METHOD:方法
  • PARAMETER:方法参数
  • CONSTRUCTOR:构造函数
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解
  • PACKAGE:包

首先,定义一个注解、和一个供注解修饰的简单Java类

定义注解格式:
  public @interface 注解名 {定义体} 例:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {
   //表示操作是那个服务哪个模块下的操作
  String module() default "xxxx服务";

  //操作的类型,添加,更新,删除
  String type() default "add";

  //操作者
  String user() default "system";

  //操作描述
  String operation() default "";

}

 二、Aspect类

import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
 
@Aspect
@Component
public class OperationLogAspect {
    private ThreadLocal<SimpleDateFormat> format = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
    //切点表达式,表示加了OperationLog注解的都是切点,路径是自定义注解的全路径
    @Pointcut("@annotation(com.alice.springboot.demo.OperationLog)")
    public void pointcut(){ 
    }
    
    @Around("@annotation(operationLog)")//拦截加了OperationLog注解的方法,也可以直接拦截上面定义的切点(@Around("pointcut()"))
    public Object operationLogRecord(ProceedingJoinPoint joinPoint, OperationLog operationLog){
        //获取请求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //响应
        ResponseResult<Object> responseResult = null;
        //判断原方法是否正常执行的标志
        boolean isNormalProcess = false;
        try{
            //返回切点处继续执行原方法,并接收原方法的返回值
            responseResult = (ResponseResult<Object>) joinPoint.proceed();    
            //如果顺利执行,那么说明原方法执行正常,就可以进行日志记录。因为,如果原方法的增删改出问题了,那么日志就不需要记录了,不用记录失败的操作。
            isNormalProcess = true;
        }catch (Throwable e){
            System.out.println("原方法报错,不需要记录日志");
        }try {
            if (isNormalProcess){
                //如果原方法正常执行完毕,那么需要记录操作日志
                saveOperationLog(joinPoint, operationLog, request);
            }
        }catch (Exception e){
            System.out.println("保存操作日志出错");
        }
        return  responseResult;
    }
    
    private void saveOperationLog(ProceedingJoinPoint joinPoint, OperationLog operationLog, HttpServletRequest request){
        //用来记录参数的值
        StringBuilder contentBuilder = new StringBuilder();
        //从切点获取切点的所有参数
        Object[] allParams = joinPoint.getArgs();
        
        for (Object param: allParams){
            contentBuilder.append(JSON.toJSONString(param) + ",");
        }
        //删除最后一个多余的逗号
        contentBuilder.delete(contentBuilder.length() - 1, contentBuilder.length());
        
        //执行数据库操作,将信息保存到数据库,笔者这里使用的是mongodb,仅供参考,主要看获取自定义注解里面的值
        Document document = new  Document();
        //获取自定义注解里面的值
        document.append("module", operationLog.module())
                .append("type", operationLog.type())
                .append("user", operationLog.user())
                .append("operation", operationLog.operation())
                .append("content", contentBuilder.toString());
        
        logDao.saveLogs("mongo collection name", document);
    }
}

 三、使用方法

package com.alice.springboot.demo;
 
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping(value = "/test")
public class OperationLogController {
    @RequestMapping(value = "/add", method = RequestMethod.POST)
    @OperationLog(module = "xxx服务", type = "add", operation = "添加xxx")
    //这里使用到了自定义注解,并且赋值了自定义注解里面的某些值,最后在aspect里面可以获取到这些值
    public ResponseResult<String> addOperation(String user, String content){
        ResponseResult<String> result = new ResponseResult<>();
        try{
            //执行添加操作
            result.setStatus(ResponseStatusEnum.SUCCESS);
            result.setMessage("添加操作成功");
        }catch (Exception e){
            result.setStatus(ResponseStatusEnum.FAIL);
            result.setMessage("添加操作失败" + e.toString());
        }
        return result;
    }
}

原文连接:https://blog.csdn.net/jmdonghao/article/details/78880899

以上是关于自定义注解的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段(vue主模板)

VSCode自定义代码片段——声明函数

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段——git命令操作一个完整流程

VSCode自定义代码片段8——声明函数

VSCode自定义代码片段1——vue主模板