自定义注解之编译时注解(RetentionPolicy.CLASS)(一)

http://blog.csdn.net/github_35180164/article/details/52121038

说到编译时注解(RetentionPolicy.CLASS)都要和注解处理器(Annotation Processor) 扯上关系,因为这里是真正体现编译时注解价值的地方。需要注意的一点是,运行时注解(RetentionPolicy.RUNTIME)源码注解(RetentionPolicy.SOURCE)也可以在注解处理器进行处理,不同的注解有各自的生命周期,根据你实际使用来确定。
注解处理器(Annotation Processor)
首先来了解下什么是注解处理器,注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

自定义注解(RetentionPolicy.CLASS)
先来定义要使用的注解,这里建一个Java库来专门放注解,库名为:annotations,和下面要创建的注解处理器分开,至于为什么要分开创建后面再说。注解库指定JDK版本为1.7,如何指定往下看。自定义注解如下:

/** 
 * 编译时注解 
 */  
@Retention(RetentionPolicy.CLASS)  
@Target(ElementType.TYPE)  
public @interface MyAnnotation {  
    String value();  
}  

定义的是编译时注解,对象为类或接口等。

定义注解处理器****
下面来定义注解处理器,另外建一个Java库工程,库名为:processors,记得是和存放注解的库分开的。注意,这里必须为Java库,不然会找不到javax包下的相关资源。来看下现在的目录结构:


这里定义一个注解处理器 MyProcessor,每一个处理器都是继承于AbstractProcessor,并要求必须复写 process() 方法,通常我们使用会去复写以下4个方法:

/** 
 * 每一个注解处理器类都必须有一个空的构造函数,默认不写就行; 
 */  
public class MyProcessor extends AbstractProcessor {  
  
    /** 
     * init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。 
     * ProcessingEnviroment提供很多有用的工具类Elements, Types 和 Filer 
     * @param processingEnv 提供给 processor 用来访问工具框架的环境 
     */  
    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);  
    }  
  
    /** 
     * 这相当于每个处理器的主函数main(),你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。 
     * 输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素 
     * @param annotations   请求处理的注解类型 
     * @param roundEnv  有关当前和以前的信息环境 
     * @return  如果返回 true,则这些注解已声明并且不要求后续 Processor 处理它们; 
     *          如果返回 false,则这些注解未声明并且可能要求后续 Processor 处理它们 
     */  
    @Override  
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
        return false;  
    }  
  
    /** 
     * 这里必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称 
     * @return  注解器所支持的注解类型集合,如果没有这样的类型,则返回一个空集合 
     */  
    @Override  
    public Set<String> getSupportedAnnotationTypes() {  
        Set<String> annotataions = new LinkedHashSet<String>();  
        annotataions.add(MyAnnotation.class.getCanonicalName());  
        return annotataions;  
    }  
  
    /** 
     * 指定使用的Java版本,通常这里返回SourceVersion.latestSupported(),默认返回SourceVersion.RELEASE_6 
     * @return  使用的Java版本 
     */  
    @Override  
    public SourceVersion getSupportedSourceVersion() {  
        return SourceVersion.latestSupported();  
    }  
}  

上面注释说的挺清楚了,我们需要处理的工作在 process()**** 方法中进行,等下给出例子。对于 getSupportedAnnotationTypes() 方法标明了这个注解处理器要处理哪些注解,返回的是一个Set 值,说明一个注解处理器可以处理多个注解。除了在这个方法中指定要处理的注解外,还可以通过注解的方式来指定(SourceVersion也一样):

@SupportedSourceVersion(SourceVersion.RELEASE_8)  
@SupportedAnnotationTypes("com.example.annotation.cls.MyAnnotation")  
public class MyProcessor extends AbstractProcessor {  
    // ...  
}  

因为兼容的原因,特别是针对Android平台,建议使用重载** getSupportedAnnotationTypes()** 和getSupportedSourceVersion()方法代替@SupportedAnnotationTypes@SupportedSourceVersion
现在来添加对注解的处理,简单的输出一些信息即可,代码如下:

@Override  
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  
    // roundEnv.getElementsAnnotatedWith()返回使用给定注解类型的元素  
    for (Element element : roundEnv.getElementsAnnotatedWith(MyAnnotation.class)) {  
        System.out.println("------------------------------");  
        // 判断元素的类型为Class  
        if (element.getKind() == ElementKind.CLASS) {  
            // 显示转换元素类型  
            TypeElement typeElement = (TypeElement) element;  
            // 输出元素名称  
            System.out.println(typeElement.getSimpleName());  
            // 输出注解属性值  
            System.out.println(typeElement.getAnnotation(MyAnnotation.class).value());  
        }  
        System.out.println("------------------------------");  
    }  
    return false;  
}  

到这里注解处理器也写好了,下面就看怎么运行它了。

运行注解处理器
在运行前,你需要在主项目工程中引入 annotations 和 **processors **这两个库(引入 **processors **库不是个好做法,后面介绍更适当的方法)。这时如果你直接编译或者运行工程的话,是看不到任何输出信息的,这里还要做的一步操作是指定注解处理器的所在,需要做如下操作:
1、在 **processors **库的 **main **目录下新建 **resources **资源文件夹;
2、在 resources文件夹下建立 META-INF/services 目录文件夹;
3、在 META-INF/services 目录文件夹下创建 javax.annotation.processing.Processor 文件;
4、在 javax.annotation.processing.Processor 文件写入注解处理器的全称,包括包路径;
来看下整个目录结构:


处理完就可以使用了,我们在项目中使用 @MyAnnotation 注解:

@MyAnnotation("Hello Annotation")  
public class MainActivity extends AppCompatActivity {  
    // ...  
}  

到这里我们重新编译下工程就应该有输出了,如果没看到输出则先清理下工程在编译,如下两个操作:


输出信息如下:

现在注解处理器已经可以正常工作了~
当然了,上面还遗留着一个问题,我们的主项目中引用了 processors 库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主项目添加了这个库会引入很多不必要的文件,为了处理这个问题我们需要引入个插件android-apt,它能很好地处理这个问题。
在介绍这个插件前,我想先介绍个好用的库AutoService,这里有个坑。

AutoService
前面在指定注解处理器的时候你会不会觉得很麻烦?那么多步骤就为添加一个注解处理器,不过没关系,AutoService 可以帮你解决这个问题。
AutoService注解处理器是Google开发的,用来生成 **META-INF/services/javax.annotation.processing.Processor **文件的,你只需要在你定义的注解处理器上添加 @AutoService(Processor.class) 就可以了,简直不能再方便了。
先给 **processors **库依赖上 AutoService,你可以直接在 AndroidStudio 工具上搜索添加,如下:


添加好以后就可以直接用了,在我们之前定义的注解处理器上使用:

apply plugin: 'com.android.application'  
  
android {  
    // ...  
    packagingOptions {  
        exclude 'META-INF/services/javax.annotation.processing.Processor'  
    }  
}  

这样就不会报错了,这是其中的一个解决方法,还有个更好的解决方法就是用上上面提到的android-apt了,下面正式登场

Android-apt
那么什么是android-apt呢?官网有这么一段描述:
The android-apt plugin assists in working with annotation processors in combination with Android Studio. It has two purposes:
1、Allow to configure a compile time only annotation processor as a dependency, not including the artifact in the final APK or library2、Set up the source paths so that code that is generated from the annotation processor is correctly picked up by Android Studio
大体来讲它有两个作用:能在编译时期去依赖注解处理器并进行工作,但在生成 APK 时不会包含任何遗留的东西
能够辅助 Android Studio 在项目的对应目录中存放注解处理器在编译期间生成的文件

这个就可以很好地解决上面我们遇到的问题了,来看下怎么用。
首先在整个工程的 **build.gradle **中添加如下两段语句:

buildscript {  
    repositories {  
        jcenter()  
        mavenCentral()  // add  
    }  
    dependencies {  
        classpath 'com.android.tools.build:gradle:2.1.2'  
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  // add  
    }  
}  

在主项目(app)的 build.gradle 中也添加两段语句:

apply plugin: 'com.android.application'  
apply plugin: 'com.neenbedankt.android-apt' // add  
// ...  
dependencies {  
    compile fileTree(include: ['*.jar'], dir: 'libs')  
    testCompile 'junit:junit:4.12'  
    compile 'com.android.support:appcompat-v7:23.4.0'  
    compile project(':annotations')  
//    compile project(':processors')  替换为下面  
    apt project(':processors')  
}  

上面提到android-apt的作用有对编译时期生成的文件处理,关于生成文件的功能就不得不提 JavaPoet

但是对于AndroidStudio 2.2使用 Java 8 功能和 Jack 工具链的问题

如果你安装官网设置Java 8 功能和 Jack 工具链的配置后遇到 Error:Could not get unknown property 'classpath' for task ':app:transformJackWithJackForInstantrunconfigDebug' of type com.Android.build.gradle.internal.pipeline.TransformTask.

那么检查你是否使用了带有apt moudle
解决方法

删除项目build.gradle文件里的
apply plugin: 'android-apt'
把apt替换成annotationProcessor
删除根目录的build.gradleclasspath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

这个不用Java8也可以这么写的,现在AS自带了注解插件

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 28,920评论 15 116
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 我好像有很多多余的时光,当我不用上课,当我没有作业,当我不用上班,当我不用完成别人交给的任务……等等,当只剩我一个...
    学着跟自己相处阅读 219评论 0 1
  • 2017.10.15 下午要送女儿去学画画,出门前,把车钥匙拿出来,拿在手上。出门,关门。瞥见门口有一袋垃圾,顺手...
    中和西阅读 231评论 0 0