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