Android自定义注解学习笔记

一、元注解

@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 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 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.


打印的LOG

注释处理器的功能非常强大,可以通过跟javapoet库配合使用生成实用的Java类。

你可能感兴趣的:(Android自定义注解学习笔记)