java--最全注解解析
- 注解
- 标准注解
- 元注解
- Target 范围
- Retention 声明周期
- Documented 说明文档
- Inherited 传播
- 注解元素
- 注解元素的值
- 注解元素的默认名称
- 注解不支持继承
- 注解解析器
- 实例--定义注解
- 注解
- 类
- 构造方法
- 属性
- 方法
- 参数
- 变量
- 实例--解析注解
- 注解
- 类
- 构造方法
- 属性
- 方法
- 参数
- 变量
github地址:
https://github.com/a18792721831/studyjdk.git
model:
先来一张图总览:
注解
Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。
标准注解
- @Override:表示当前的方法定义将覆盖超类中的方法。如果子类方法拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
- @Deprecated:如果使用了注解为它的元素,编译器会发出警告信息。
- @SuppressWWarnings:关闭不当的编译器警告信息。
元注解
元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
Target 范围
@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标。
Retention 声明周期
Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在 class 文件中有效(即 class 保留)
- RUNTIME:在运行时有效(即运行时保留)
Documented 说明文档
@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。
Inherited 传播
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。
注解元素
- 所有的基本类型
- String
- Class
- enum
- Annotation
注意,注解中不允许使用任何包装类型。 但是因为自动拆装箱的存在,这个基本上不是问题,只是需要注意,定义时,定义为基本类型就行。
注解元素的值
注解元素要么有值,要么有默认值。
换句话说,只要定义了注解元素,那么在使用时,就不能使注解元素空着。
注解元素的默认名称
一般来说 ,注解元素的值在指定的时候都必须指明是哪个注解元素。
唯一例外:注解元素的名称是value,而且本次使用只指定这一个注解元素(其他注解元素有默认值,或者只有一个注解元素)。
注解不支持继承
不能使用关键字extends来继承某个@interface。不过,注解元素可以是注解,可以使用注解嵌套的方式,复用已有的注解。
但是这样会大大的增加注解使用的难度。
注解解析器
注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。
最后根据注解元素的值,做不同的操作。
实例–定义注解
注解
@Target(ElementType.ANNOTATION_TYPE)@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "this")
public @interface StudyAnno {
public String name() default "studyAnno";
}
类
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "studyClass")
@StudyClass(name = "this")
public @interface StudyClass {
public String name() default "studyClass";
}
构造方法
@Target(ElementType.CONSTRUCTOR)@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyCons")
@StudyClass(name = "studyCons")
public @interface StudyCons {
@StudyMethod
public String name() default "studyCons";
}
属性
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyField")
@StudyClass(name = "studyField")
public @interface StudyField {
@StudyMethod(name = "studyField")
public String name() default "studyField";
}
方法
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyMethod")
@StudyClass(name = "studyMethod")
public @interface StudyMethod {
@StudyMethod(name = "name")
public String name() default "studyMethod";
}
参数
@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyParam")
@StudyClass(name = "studyParam")
public @interface StudyParam {
@StudyMethod(name = "studyParam")
public String value() default "";
}
变量
@Target(ElementType.LOCAL_VARIABLE)@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyVar")
@StudyClass(name = "studyVar")
public @interface StudyVar {
@StudyMethod(name = "studyVar")
public String name() default "studyVar";
}
实例–解析注解
创建普通类,也就是使用注解的类
这两个普通的类有这样的一个特点:
子类没有使用任何注解
这个主要是验证注解的继承特性。
这是注解和类的一些描述类型的关系:
可以看到最顶层的是AnnotatedElelment接口。
注解解析器的处理流程都相同:注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。
所以,创建一个公共的方法,这个公共的方法,就是处理的是AnnotatedElelment的对象。
/*** 注解解析
*
* @param annotatedElement 需要处理的AnnotatedElement对象
* @param annClazz 需要处理的注解类型
*/
private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
// 获取使用注解的对象上面的注解 (StudyClass对象)
Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
// 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
// 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
try {
// 获取注解对象中的注解元素 (name方法)
Method name = x.getClass().getDeclaredMethod("name", null);
// 执行注解元素,获取注解元素值 ("people")
Object nameValue = name.invoke(x, null);
// 获取到注解元素值之后做的操作
System.out.println(annClazz.getSimpleName() + " , name = " + nameValue);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
});
}
这个方法是注解解析的核心,我们创建了这个公共的方法后,只需要传入需要解析的对象即可:类、注解、构造、属性、方法、参数等等。
为了方便在控制台观察,我们增加一个日志方法,日志方法是注解解析方法的前置方法。但是这两个方法同时可用:
/*** 记录日志
*
* @param annotatedElement
* @param annClass
* @param objClass
*/
private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
parse(annotatedElement, annClass);
}
日志方法打印一些友好查看的信息,然后转调核心解析方法。
注解
/*** @param object 普通对象
*/
private static void parseAnno(Object object) {
// 获取普通的所有的注解
Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
// 获取注解的注解对象
// 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
// 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
// 将含有注解信息的代理对象进行解析
parse(x.annotationType(), StudyAnno.class, object.getClass());
});
}
解析注解需要注意,传入核心解析的对象应该是注解的注解对象。
在主方法调用:
public static void main(String[] args) {People people = new People("xiaomeiS");
Man man = new Man("hah");
// getDeclaredAnnotations:获取直接标注在指定对象上面的注解
// getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
parseAnno(people);
parseAnno(man);
}
输出如下:
类
/*** @param object 普通对象
*/
private static void parseClass(Object object) {
// 获取普通对象的class对象
parse(object.getClass(), StudyClass.class, object.getClass());
}
执行结果
构造方法
/*** 解析构造
*
* @param object
*/
private static void parseCons(Object object) {
// 获取普通对象的Constructor对象
Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
}
执行结果
属性
/*** 解析属性
*
* @param object
*/
private static void parseField(Object object) {
// 获取对象的全部属性对象
Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> {
parse(x, StudyField.class, object.getClass());
});
}
执行结果:
因为People里面的属性都是private的,Man继承于People,但是私有属性没有被继承。
我们在People中增加其他三个权限的属性:
然后重新执行解析:
方法
/*** 解析方法
*
* @param object
*/
private static void parseMethod(Object object) {
// 获取对象的全部方法
Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> {
parse(x, StudyMethod.class, object.getClass());
});
}
执行结果:
虽然Peopl的方法是公有的,Man继承于People,那么,Man也能够调用公有方法,但是StudyMenthod注解是不可被继承的。
换句话说:因为StudyMenthod没有使用Inherited修饰,所以Man从People继承了方法,但是没有继承得到注解。
参数
在People中,只有构造方法有参数,而且我们只在这一个地方用到了StudyParam注解。所以,我们应该首先得到构造方法,然后在获取参数对象。
/*** 解析参数
*
* @param object
*/
private static void parseParam(Object object) {
// 获取普通对象的构造器、方法(全部可以有参数的方法)
Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
Method[] declaredMethods = object.getClass().getDeclaredMethods();
Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
}
执行结果
纳尼,竟然异常了。嗯嗯。
看看异常堆栈吧,是没有找到方法的异常。
为什么呢?
调试一次吧:
原来我们在注解中定义的方法名称不是name了,而是value了。
但是在代码中直接写死使用name,所以就异常了。
所以这里我们获取注解对象中全部的方法,因为注解中的方法都是没有参数的,所以获取全部的方法,过滤掉有参数的方法。
==还有一点,因为获取到的注解对象是代理对象,java中对象都继承了Object,那么就同时继承了Object的公共方法,同时还有annotationType方法。==这个方法是调试的时候发现的,可能和jdk代理有关吧。
然后我们将注解中的方法名,也就是注解参数名也输出:
再次执行
这种方式,对于原来的name也是支持的:
=前面的就是注解的元素的名字,= 后面就是注解元素的值。
(吐槽一句:高亮显示=,结果因为高亮的markdown的表示是= = ,结果直接显示5个 =?)
同样因为StudyParam注解没有被Inherited修饰,所以Man中不会继承有这个注解。
变量
因为放在变量上面的注解,在类编译的过程中,局部变量就会被编译器处理,所以,局部变量的注解,即使设置它的生命周期是RUNTIME或者是CLASS,注解实际生效的生命周期还是SOURCE。
首先我们找到class文件,然后进行反编译查看:
不要尝试使用javac编译单独的People类,会出现注解类找不到的异常。
然后使用javap -V 反编译查看:
我们的局部变量在编译时,就被处理了。也就是说,在编译时,已经没有了StudyVar注解了。
局部变量被放到了局部变量表中:
至此,全部完成。
最后附上全部解析源码:
package com.study.studyannotation;import com.study.studyannotation.anno.*;
import com.study.studyannotation.beans.Man;
import com.study.studyannotation.beans.People;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
/**
* @author
* @Date 2020/8/14
*/
public class Main {
public static void main(String[] args) {
People people = new People("xiaomeiS");
Man man = new Man("hah");
// getDeclaredAnnotations:获取直接标注在指定对象上面的注解
// getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
parseAnno(people);
parseAnno(man);
System.out.println();
parseClass(people);
parseClass(man);
System.out.println();
parseCons(people);
parseCons(man);
System.out.println();
parseField(people);
parseField(man);
System.out.println();
parseMethod(people);
parseMethod(man);
System.out.println();
parseParam(people);
parseParam(man);
}
/**
* 解析组合注解
*
* @param object 普通对象
*/
private static void parseAnno(Object object) {
// 获取普通的所有的注解
Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
// 获取注解的注解对象
// 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
// 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
// 将含有注解信息的代理对象进行解析
parse(x.annotationType(), StudyAnno.class, object.getClass());
});
}
/**
* 解析类
*
* @param object 普通对象
*/
private static void parseClass(Object object) {
// 获取普通对象的class对象
parse(object.getClass(), StudyClass.class, object.getClass());
}
/**
* 解析参数
*
* @param object
*/
private static void parseParam(Object object) {
// 获取普通对象的构造器、方法(全部可以有参数的方法)
Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
Method[] declaredMethods = object.getClass().getDeclaredMethods();
Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
// 获取参数
Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
});
}
/**
* 解析构造
*
* @param object
*/
private static void parseCons(Object object) {
// 获取普通对象的Constructor对象
Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
}
/**
* 解析属性
*
* @param object
*/
private static void parseField(Object object) {
// 获取对象的全部属性对象
Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> parse(x, StudyField.class, object.getClass()));
}
/**
* 解析方法
*
* @param object
*/
private static void parseMethod(Object object) {
// 获取对象的全部方法
Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> parse(x, StudyMethod.class, object.getClass()));
}
/**
* 记录日志
*
* @param annotatedElement
* @param annClass
* @param objClass
*/
private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
parse(annotatedElement, annClass);
}
/**
* 注解解析
*
* @param annotatedElement 需要处理的AnnotatedElement对象
* @param annClazz 需要处理的注解类型
*/
private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
// 获取使用注解的对象上面的注解 (StudyClass对象)
Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
// 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
// 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
// 获取注解对象中的注解元素 (name方法)
// 注解中的方法都是没有参数的
// 同时因为代理对象的缘故,还需要去掉Object的2个无参公共方法:toString,hashCode,以及class的annotationType方法
List<String> methodList = Arrays.asList("toString", "hashCode", "annotationType");
Arrays.stream(x.getClass().getDeclaredMethods()).filter(y -> y.getParameterCount() == 0)
.filter(y -> !methodList.contains(y.getName()))
.forEach(y -> {
// 执行注解元素,获取注解元素值 ("people")
try {
Object nameValue = y.invoke(x, null);
System.out.println(annClazz.getSimpleName() + " , " + y.getName() + " = " + nameValue);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 获取到注解元素值之后做的操作
});
});
}
}
执行结果