一、注解的作用

  • 注解是一种元数据形式。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似。
  • 注解用来修饰,类、方法、变量、参数、包。
  • 注解不会对所修饰的代码产生直接的影响。

二、创建自定义注解

2.1 基本定义

  • 首先使用 @interface声明注解名称
  • 然后,使用@Retention,@Target等元注解标注注解的生命周期和作用元素
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.Type)
public @interface FirstAnnotation{
}

2.2 示例 – 类级别的注解

定义注解

@Target(ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Custom1 {
   
    String[] values();
}

测试注解

@Custom1(values = {
   "V1","V2"})
public class MainTest {
   

    @Test
    public void t1(){
   

        MainTest t = new MainTest();

        boolean present = t.getClass().isAnnotationPresent(Custom1.class);

        if (present) {
   

            Custom1 ca = t.getClass().getAnnotation(Custom1.class);
            if (ca != null) {
   
                for (String value : ca.values()){
   
                    System.out.println(value);
                }
            }
        }

    }

}

控制台打印

V1
V2

2.3 示例 – 方法级别的注解

定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
   

}

使用注解,方法之上

public class AnnotationRunner {
   

    public void method1() {
   
        System.out.println("method1");
    }

    @CanRun
    public void method2() {
   
        System.out.println("method2");
    }

    public void method3() {
   
        System.out.println("method3");
    }

    @CanRun
    public void method5() {
   
        System.out.println("method4");
    }

}

测试注解

public class MainTest {
   

    @Test
    public void t1(){
   

        AnnotationRunner runner = new AnnotationRunner();
        Method[] methods = runner.getClass().getMethods();

        for (Method method : methods) {
   
            CanRun annos = method.getAnnotation(CanRun.class);
            if (annos != null) {
   
                try {
   
                    method.invoke(runner);
                } catch (Exception e) {
   
                    e.printStackTrace();
                }
            }
        }
    }
}

控制台打印

method2
method4

2.4 示例 – 字段属性级别的注解

定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CanRun {
   

}

使用注解,方法之上

public class AnnotationRunner {
   

    public void method1() {
   
        System.out.println("method1");
    }

    @CanRun
    public void method2() {
   
        System.out.println("method2");
    }

    public void method3() {
   
        System.out.println("method3");
    }

    @CanRun
    public void method5() {
   
        System.out.println("method4");
    }

}

测试注解

public class MainTest {
   

    @Test
    public void t1(){
   

        AnnotationRunner runner = new AnnotationRunner();
        Method[] methods = runner.getClass().getMethods();

        for (Method method : methods) {
   
            CanRun annos = method.getAnnotation(CanRun.class);
            if (annos != null) {
   
                try {
   
                    method.invoke(runner);
                } catch (Exception e) {
   
                    e.printStackTrace();
                }
            }
        }
    }
}

控制台打印

method2
method4

三、注解的使用场景

3.1 JDK中的常用注解

@Override: 表示注解修饰的方法必须满足重写的规则
@Deprecated: 表示成员过时,编译器可以在程序运行的时候获取到该注解
@SupressWarnings: 表示忽略编译器的警告
@FunctionalInterface: 表示该接口是一个函数式接口,并且可以作为Lambda表达式参数传入

还有,几个元注解,用于定义注解的
@Retention: 表示对它所标记的元素的生命周期(参考的范围看RetentionPolicy枚举类)
@Target: 表示标记定义的注解可以和什么目标元素绑定
@Inherited: 表示该注解可以被继承
@Document: 表示该注解可以被生成API文档

3.2 Spring中的常用注解

@RestController
@RequestMapping
@PostMapping
@RequestBody

这里是Spring中所有的注解链接

3.3 自定义注解的使用场景

3.3.1 AOP + @CusLog,通过自定义注解实现 >>操作日志 --> DB数据库

- 首先是有日志的Model定义,Dao层的save实现,这里不介绍太多了…

@Data
@Entity
@Table(name = "operate_log")
public class OperateLog {
   
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   private String recordId; // 操作数据id


   private String module;// 模块名称

   private String business;// 业务方法描述

   private String opType;// 操作类型

   private Long userId;// 操作人

   private String userName;// 操作人姓名

   private String params;// 操作数据

   /** * 创建时间 */
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   private Date createTime;

(2) - 定义注解

@Target({
   ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface SystemLog {
   
   /** * 操作描述 业务名称business * * @return */
   String description() default "";

   /** * 操作模块 * * @return */
   OperateModule module();

   /** * 操作类型 create modify delete * * @return */
   OperateType opType();

   /** * 主键入参参数名称,入参中的哪个参数为主键 * * @return */
   String primaryKeyName() default "";

   /** * 主键在参数中的顺序,从0开始,默认0 */
   int primaryKeySort() default 0;

   /** * 主键所属类 * * @return */
   Class<?> primaryKeyBelongClass();

}

(3) - 使用注解(处理注解),AOP / Aspect + 反射(通过注解获取对象的字段,方法等信息)

@Aspect
@Component
@Slf4j
public class WebLogAspect {
   
   @Autowired
   private OperateLogRepository logRepository;

   @Pointcut("execution(public * demo1.controller..*.*(..)) "
           + " && @annotation(demo1.log.SystemLog)")
   public void webLog() {
   
   }

   @Around("webLog()")
   public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
   
       // log.info("环绕通知开始........");
// String username = SecurityUtils.getCurrentUserName();

       String username = "ss";
       // 入参 value
       Object[] args = joinPoint.getArgs();
       // 入参名称
       String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
       Map<String, Object> params = new HashMap<>();
       // 获取所有参数对象
       for (int i = 0; i < args.length; i++) {
   
           if (null != args[i]) {
   
               if (args[i] instanceof BindingResult) {
   
                   params.put(paramNames[i], "bindingResult");
               } else {
   
                   params.put(paramNames[i], args[i]);
               }
           } else {
   
               params.put(paramNames[i], "无");
           }
       }
       Map<String, Object> values = getControllerAnnotationValue(joinPoint);
       String opType = values.get("opType").toString();
       String module = values.get("module").toString();
       String business = values.get("business").toString();
       String primaryKeyName = values.get("primaryKeyName").toString();
       int primaryKeySort = Integer.parseInt(values.get("primaryKeySort").toString());
       Class<?> primaryKeyBelongClass = (Class<?>) values.get("primaryKeyBelongClass");

       Object primaryKeyValue = null;
       if (StringUtils.isNotBlank(primaryKeyName) && OperateType.valueOf(opType) == OperateType.delete) {
   
           primaryKeyValue = args[primaryKeySort];
       }
       // 切面返回值
       Object returnValue = joinPoint.proceed();
       if (OperateType.valueOf(opType) != OperateType.delete) {
   
           // 主要目的是为了获取方法保存成功的返回数据格式,以获取保存成功的数据id
           // 此处要限制新增返回成功的格式,return ok("具体操作信息", new MapBean("此处为实体主键属性名称",
           // primaryKeyValue));
           // 不然自己也可定义格式,进行拆分获取主键
           primaryKeyName = getPrimaryKeyName(primaryKeyBelongClass).toString();
           primaryKeyValue = ReflectUtils.dynamicGet(returnValue, primaryKeyName);
           if (primaryKeyValue == null || primaryKeyValue.toString().equals("")) {
   // 处理service层返回ResultBean
               Object result = ReflectUtils.dynamicGet(returnValue, "result");
               if (result != null) {
   
                   primaryKeyValue = ReflectUtils.dynamicGet(result, primaryKeyName);
               } else {
   
                   primaryKeyValue = args[primaryKeySort];
               }
           }
       }

       OperateLog operateLog = new OperateLog();

       //
       if (JSONUtil.toJsonStr(params).length() <= 2000) {
   
           operateLog.setData(JSONUtil.toJsonStr(params));
       }

       operateLog.setUserId(1L);
       operateLog.setUserName(username == null ? "系统" : username);
       operateLog.setModule(module);
       operateLog.setOpType(opType);
       operateLog.setBusiness(business);

       String recordId = null;
       if (primaryKeyValue instanceof Object[]) {
   
           recordId = Arrays.toString((Object[]) primaryKeyValue);
       } else {
   
           recordId = primaryKeyValue.toString();
       }
       operateLog.setRecordId(recordId);
       operateLog.setCreateTime(new Date());
       logRepository.save(operateLog);
       log.info(">>>操作日志:{}", operateLog);
       return returnValue;
   }

   /** * 获取class的主键字段名 主键值 */
   @SuppressWarnings({
   "rawtypes", "unchecked"})
   private static Object getPrimaryKeyName(Class<?> clazz) throws Exception {
   
       Object param = null;
       // 递归获取父子类所有的field
       Class tempClass = clazz;
       // 当父类为null的时候说明到达了最上层的父类(Object类
       while (tempClass != null && !StringUtils.equals(tempClass.getName().toLowerCase(), "java.lang.object")) {
   
           Field[] fields = tempClass.getDeclaredFields();
           for (Field field : fields) {
   
               String fieldName = field.getName();
               // boolean类型不必判断,因实体里包含boolean类型的属性,getter方法是以is开头,不是get开头
               if (field.getType().equals(Boolean.class) || field.getType().getName().equals("boolean")) {
   
                   continue;
               }
               if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
   
                   continue;
               }
               String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
               Method method = tempClass.getDeclaredMethod(getterMethod);

               // 字段上是否存在@Id注解
               Object primaryAnnotation = field.getAnnotation(Id.class);// for hibernate
               if (primaryAnnotation == null)
                   primaryAnnotation = field.getAnnotation(org.springframework.data.annotation.Id.class);// for spring
               // data
               // getter方法上是否存在@Id注解
               if (primaryAnnotation == null)
                   primaryAnnotation = method.getAnnotation(Id.class);
               if (primaryAnnotation == null)
                   primaryAnnotation = method.getAnnotation(org.springframework.data.annotation.Id.class);
               // 存在@Id注解,则说明该字段为主键
               if (primaryAnnotation != null) {
   
                   /* String primaryKeyName = field.getName(); */
                   param = field.getName();
                   break;
               }
           }
           if (param != null && StringUtils.isNotBlank(param.toString())) {
   
               break;
           }
           // 得到父类赋值给tempClass
           tempClass = tempClass.getSuperclass();
       }
       if (param == null) {
   
           throw new Exception(clazz.getName() + "实体,未设置主键");
       }
       return param;
   }

   /** * 获取@SystemLog 注解上信息 * * @param joinPoint * @return map * @throws Exception */
   @SuppressWarnings("rawtypes")
   public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
   
       String targetName = joinPoint.getTarget().getClass().getName();
       String methodName = joinPoint.getSignature().getName();
       Object[] arguments = joinPoint.getArgs();
       Class targetClass = Class.forName(targetName);
       Method[] methods = targetClass.getMethods();
       Map<String, Object> map = new HashMap<>();
       for (Method method : methods) {
   
           if (method.getName().equals(methodName)) {
   
               Class[] classes = method.getParameterTypes();
               if (classes.length == arguments.length) {
   
                   // 取入参数据
                   String description = method.getAnnotation(SystemLog.class).description();
                   String module = method.getAnnotation(SystemLog.class).module().name();
                   String opType = method.getAnnotation(SystemLog.class).opType().name();
                   String primaryKeyName = method.getAnnotation(SystemLog.class).primaryKeyName();
                   int primaryKeySort = method.getAnnotation(SystemLog.class).primaryKeySort();
                   Class<?> clazz = method.getAnnotation(SystemLog.class).primaryKeyBelongClass();
                   map.put("module", module);
                   map.put("opType", opType);
                   map.put("business", description);
                   map.put("primaryKeyName", primaryKeyName);
                   map.put("primaryKeySort", primaryKeySort);
                   map.put("primaryKeyBelongClass", clazz);
                   break;
               }
           }
       }
       return map;
   }

   @AfterReturning("webLog()")
   public void doAfterReturning(JoinPoint joinPoint) {
   
       // 处理完请求,返回内容
       // log.info("WebLogAspect.doAfterReturning()");
   }
}

四、Spring下所有的注解,及其用法,持续更补