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<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    //用于指定对应的注释,返回一个String集合(可被上面的注释代替)
    @Override
    public Set<String> 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.


打印的LOG

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容