一、元注解
@interface是一种自定义的注解类型,他可以由四种元注解修饰,分别是@Target、@Retention、@Documented、@Inherited。
//如何使用元注解修饰创建的自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyFirstAnnotation {
String name() default "author";
int age();
}
在其他类中使用自定义的注解:
/*@interface MyFirstAnnotation后的MyFirstAnnotation就是自定义注解的名字。
*在创建自定义注解时如果没有设置默认值的话就必须进行赋值操作(由于age没有默认值,所以这边必须赋值)
*/
@MyFirstAnnotation(age = 23)
public class Demo {
}
1、@Target
@target:主要用来设置注解的使用范围
public enum ElementType {
//主要用于修饰类,接口,枚举类型等
TYPE,
//修饰作用域
FIELD,
//修饰方法
METHOD,
//修饰参数
PARAMETER,
//修饰构造函数
CONSTRUCTOR,
//修饰局部变量
LOCAL_VARIABLE,
//用于描述包
PACKAGE,
}
2、@Retention
@Retention:主要用于控制注解的生命周期,主要有三种类型:SOURCE、CLASS、RUNTIME。
public enum RetentionPolicy {
//源码级别,只存在于源码中,用于与编译器交互进行代码检测(@Override,@SuppressWarings等)
//一般用于生成源码级别的框架
SOURCE,
//字节码级别,注解的信息会被保留在class文件中,但是不会存在与JVM中
CLASS,
//运行时级别,存在于JVM虚拟机中,主要用于反射来获取相关的信息。一般用于生成运行级别的框架
RUNTIME;
}
3、@Documented与@Inherited
@Documented:被修饰的注解会生成到javadoc中
@Inherited:如果父类被注解修饰的话,子类会继承这个注释
public class Demo {
@MyFirstAnnotation(age = 23)
private static class Father {
}
private static class Child extends Father{
}
public static void main(String... args){
Father child=new Child();
//isAnnotationPresent可以用来判断当前类是否使用这个注解
if (child.getClass().isAnnotationPresent(MyFirstAnnotation.class)){
System.out.println("isAnnotationPresent:true");
}
}
}
二、反射机制运行处理的注解
自定义的注解主要有两种形式,一种是通过反射来获取对象相应的注释,另一种是通过注释处理器在编译时处理注解。
运用反射机制去获取注释对象,要求注释必须设置为@Retention(RetentionPolicy.RUNTIME),即在JVM运行时也要保存对应的注释。
首先定义三个注释,更别用于修饰类,方法,成员变量
@Target(ElementType.TYPE)//用于修饰类
@Retention(RetentionPolicy.RUNTIME)
public @interface MyFirstAnnotation {
String name() default "author";
}
@Target(ElementType.FIELD)//用于修饰成员变量
@Retention(RetentionPolicy.RUNTIME)
public @interface MySecondAnnotation {
String gender() default "male";
}
@Target(ElementType.METHOD)//用于修饰方法
@Retention(RetentionPolicy.RUNTIME)
public @interface MyThirdAnnotation {
String likeFood() ;
String work() ;
}
然后在创建的保存用户信息的类当中使用新建的注释
@MyFirstAnnotation(name = "lilei")
public class User {
@MySecondAnnotation(gender = "male")
public String userInfo = "";
@MyThirdAnnotation(likeFood = "milk", work = "teacher")
public void getOtherInformation() {
}
}
因为Class,Method,Field都实现了AnnotatedElement接口,所以可以调用isAnnotationPresent()方法去判断当前对象是否被对应的注释给修饰,也可以通过getAnnotation()获取对应注释的实例。
void getUserInfo(String className) {
Class c =Class.forName(className);
//通过isAnnotationPresent()方法判断是否被MyFirstAnnotation给修饰
if (c.isAnnotationPresent(MyFirstAnnotation.class)) {
//通过getAnnotation()获取对应注释的实例,接着通过调用对应的方法就可以获取name值。
MyFirstAnnotation annotation = (MyFirstAnnotation) c.getAnnotation(MyFirstAnnotation.class);
name.setText(annotation.name());
}
//field和method的获取对应注释实例的过程与Class是一样的
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(MySecondAnnotation.class)) {
MySecondAnnotation annotation = field.getAnnotation(MySecondAnnotation.class);
sex.setText(annotation.gender());
}
}
for (Method method : c.getMethods()) {
MyThirdAnnotation annotation = method.getAnnotation(MyThirdAnnotation.class);
if (annotation != null) {
like.setText(annotation.likeFood());
work.setText(annotation.work());
}
}
}
除了上面介绍的isAnnotationPresent()和getAnnotation(),AnnotatedElement接口中还有getAnnotations()和getDeclaredAnnotations()两个方法,分别用于获取该对象的所有注释和该对象上直接存在的所有注释(不包括父类中Inherited修饰的注解)。
三、注释处理器来处理注释
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(由于是在编译期间就开始处理注释,因此注释的生命周期@Retention(***)可以设置为三种中的任意一种)。注解处理器的主要作用就是解析注解,获取注解相对应的值,然后以此为基础进行逻辑操作。
使用注释处理器首先需要创建一个首先创建一个Java Library,并且引用auto-Service的库。
compile 'com.google.auto.service:auto-service:1.0-rc3'
然后创建注释处理器的类,需要继承AbstractProcessor,
//autoService主要用于向javac注册我们自定义的注释解释器的(当然我们也可以自己定义注释解释器)
@AutoService(Processor.class)
//用于确定我们使用的java版本,在这里我们设置为java7(使用这个和下面两个注解可以代替
//getSupportedAnnotationTypes()和getSupportedSourceVersion()方法)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//用于指定相应的注释(需要完整的包名)
@SupportedAnnotationTypes("com.example.MyFourthAnnotation")
public class MyProcessor extends AbstractProcessor {
//编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,
//通过该参数可以获取到很多有用的工具类: Elements , Types , Filer 等等
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
//Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑(比如生成java文件)
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
//用于指定对应的注释,返回一个String集合(可被上面的注释代替)
@Override
public Set getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
//用来指定你使用的Java版本(可被上面的注释代替)
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
在init()方法中可以获取到许多工具类,通过这些工具类,我们可以打印日志,进行java文件的创建与写入(不能在原有的java文件上进行修改),或者使用Elements进行获取包名等操作。
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//用于创建java文件,并写入它
Filer mFiler = processingEnv.getFiler();
//一些实用的方法
Elements mElements = processingEnv.getElementUtils();
//用于打印具体的消息(通过messager来打印消息)
Messager mMessager = processingEnv.getMessager();
}
在process()中的roundEnv可以获取到所有使用MyFourthAnnotation注释的元素(实现了AnnotatedElement接口),因此可以轻松拿到注释的实例。然后就可以根据需求做对应的操作。这边只是做了简单的打印工作。
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取所有使用MyFourthAnnotation注释的元素(这边的元素可以是类,方法,变量等等)。
for (Element element : roundEnv.getElementsAnnotatedWith(MyFourthAnnotation.class)) {
MyFourthAnnotation annotation = element.getAnnotation(MyFourthAnnotation.class);
int id = annotation.value();
//设置为Diagnostic.Kind.ERROR时,会编译不过去,报错
mMessager.printMessage(Diagnostic.Kind.NOTE, "MyFourthAnnotation---->value:" + id);
}
return false;
}
在app编译的过程中会打印下面的log.
注释处理器的功能非常强大,可以通过跟javapoet库配合使用生成实用的Java类。