最近在看公司的一个数据推送的源码,发现很多地方用到了自定义注解,就很好奇注解到底能做什么,仅仅定义了一个 @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 中
故我们的代码执行,实际上一定是代理类在执行
评论