最近在看公司的一个数据推送的源码,发现很多地方用到了自定义注解,就很好奇注解到底能做什么,仅仅定义了一个 @interface 就把很多事情都做完了。故接下来从四个方向去认识注解
注解是什么
   Java 注解(Annotation)是 Java 语言提供的一种元数据(metadata)机制,它允许在代码中添加额外的信息和说明。注解以@符号开头,紧跟注解的名称和可选的参数。常见的元数据注解有:
@Target:用于指定注解的适用目标,如类、方法、字段等
@Override:用于标记一个方法覆盖(重写)了父类中的方法。
    @Deprecated:用于标记一个方法、类或字段已过时,不推荐使用。
    @FunctionalInterface:用于标记一个接口是函数式接口,即只包含一个抽象方法的接口。 
    @Retention:用于指定注解的保留策略,即注解在编译时、类加载时还是在运行时可见
@Documented:用于指定注解将包含在 Java 文档中
@Inherited:用于指定注解是否可被继承。
注解能做什么
- 文档:可以利用 @Deprecated 注解生成接口的 yapi 等相关文档 
- 代码分析:通过代码里标识的元数据对代码进行分析【使用反射】 
- 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override 等】 比如说 @Test 
注解的使用
 要使用注解之前,要明确想用这个注解做什么,然后定义其属性,以及在什么阶段进行处理
 比如说我现在要定义一个入参校验器注解
 @Target(ElementType.FIELD) //作用在字段上@Retention(RetentionPolicy.RUNTIME) //在程序运行期间public @interface FieldValidation {    int maxLength() default Integer.MAX_VALUE; //字段最大长度    String regex() default ""; //正则匹配    boolean required() default false; //是否需要当前参数}
   复制代码
 当前注解要作用在哪个类上面
 public class Person {    @FieldValidation(maxLength = 20, required = true, regex = "[A-Za-z]+")    private String name;
    @FieldValidation(maxLength = 20)    private String address;
    public String getName() {        return name;    }
    public void setName(String name) {        this.name = name;    }
    public String getAddress() {        return address;    }
    public void setAddress(String address) {        this.address = address;    }}
   复制代码
  校验器:
 public class FieldValidator {  	public static void validate(Object obj) throws IllegalArgumentException, IllegalAccessException {      //获取当前对象的class实例      Class<?> clazz = obj.getClass();      //获取定义的字段      Field[] fields= clazz.getDeclaredFields();      for(Field field:fields){          //字段上的注解是否为当下定义的注解          if(field.isAnnotationPresent(FieldValidation.class)){              //获取注解对象              FieldValidation annotation = field.getDeclaredAnnotation(FieldValidation.class);              field.setAccessible(true);              Object value = field.get(obj);              if(annotation.required() && value == null){                  throw new IllegalArgumentException("field" +field.getName() +"is required");
              }              if(value instanceof  String && ((String) value).length()>annotation.maxLength()){                  throw new IllegalArgumentException("field  "+field.getName()+" exceeds maxNum length");              }
              if(annotation.regex().length()>0 && value instanceof String){                  String regex = annotation.regex();                  if(!((String)value).matches(regex)){                      throw new IllegalArgumentException("field"+field.getName() +"does not match regex pattern");                  }              }
              if(value instanceof  Integer && ((Integer)value >10 || (Integer)value<0)){                  throw new IllegalArgumentException("field" +field.getName() +"integer exceeds maxNum length");              }          }      }	
}
   复制代码
  接下来写一个 test case 进行测试
 @Test    void testFieldValidation() {        Person person = new Person();        person.setName("John");        person.setAddress("123 Main Street");        person.setIdNumber(8);        FieldValidator fieldValidator = new FieldValidator();        try {            fieldValidator.validate(person);            System.out.println("Validation passed");        } catch (IllegalAccessException e) {            System.out.println("Validation field"+ e.getMessage());        }
    }
   复制代码
 
注解的底层
在上面校验器代码中,有一个点 
当前获取的 annotation 就是 @Interface 定义的 FieldValidation 吗
我们知道是,所有的注解的父类都是 annotaion 这个接口 , 其实 FieldValidation  也没有实现,只是定义了几个元素,按照 java 的规范,接口一定要实现的,那么现在要去看下这个实现的。
debug ---------------
proxy 好家伙,这不是动态代理的节奏吗
那么跟进去 field.getDeclaredAnnotation 进去看看
其内部实现要看 declaredAnnotations()
declaredAnnotations 其实是一个 map 如下:
 Map<Class<? extends Annotation>, Annotation> declaredAnnotations;  
很明显,一开始这个 map 是空  并且需要进行解析注解
故问题就交给 AnnotationParser.parseAnnotations 来处理了
AnnotationParser.parseAnnotations 有三参数
- annotation: 字节码数组,java 内部一般先将.java 转为.class 的字节码文件 
- ConstantPool:常量池 
又将其委托给内部的 parseAnnotations2 处理
做了一堆事情后,最后是要走到 annotationMap 将数据存储下来
这个方法里面是走了一个代理工厂的方式创建数据了,最后将类实现保存到 annotations 这个 map 中
故我们的代码执行,实际上一定是代理类在执行
评论