Java之 编译时注解

说明

在现阶段的各种开发中,注解也是越来越流行了,比如ButterKnife,Retrofit,Dragger,EventBus等,都是选择使用注解来配置。运行时注解在Annotation详解中做了说明,但是运行时注解由于性能问题被一些人所诟病。编译时注解的核心依赖APT(Annotation Processing Tools)实现,原理是在某些代码元素上(如类型、函数、字段等)添加注解,编译时@Retention为CLASS的Annotation,由APT自动解析的。APT在编译时根据resources资源文件夹下的META-INF/services/javax.annotation.processing.Processor自动查找所有继承自AbstractProcessor的类,然后调用他们的process方法去处理。

编译时Annotation指的是@Retention为CLASS的Annotation,由编译器自动解析。需要最的事情是:

  • 自定义类集成自 AbstractProcessor
  • 重写其中的 process 函数

编译器在编译时自动查找所有继承自 AbstractProcessor 的类,然后调用他们的 process 方法去处理。

创建

新建一个编译时注解:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ClassAnnotation {
    String author();
}

注解添加到类上:

@ClassAnnotation(author = "Remer")
public class TestAnnotation {

    @MyAnnotation(name = "test", value = "这是一个方法注解")
    public static void test(){

    }
}

解析器

@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor {
    private Filer filer;

    @Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }

    @Override public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    @Override public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }
}

其中AbstractProcessor有四个方法需要理解

  • init(ProcessingEnvironment processingEnvironment): 每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,Types和Filer。
  • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
  • getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
  • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 7的话,你也可以返回SourceVersion.RELEASE_7,我推荐你使用前者。

其中后两个函数,在jdk1.7以后就可以是用下面两个注解来取代
@SupportedAnnotationTypes({"com.example.ClassAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)

@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.example.ClassAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ClassProcessor extends AbstractProcessor {
    private Filer filer;

    @Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

其中 @AutoService 是Google提供的一个插件来帮助我们更方便的注册注解处理器,用来生成META-INF/services/javax.annotation.processing.Processor文件内容的。
Gradle引入方式:

compile 'com.google.auto.service:auto-service:1.0-rc2'

地址:auto-service

注解解析

如果想要自动的生成代码,就不得不提JavaPoet了。

JavaPoet is a Java API for generating .java source files.
Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。这个框架功能非常有用,我们可以很方便的使用它根据注解、数据库模式、协议格式等来对应生成代码。通过这种自动化生成代码的方式,可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

GitHub 地址:JavaPoet
对注解解析的代码可以跟新为:

@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.example.ClassAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ClassProcessor extends AbstractProcessor {
    private Filer filer;

    @Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(ClassAnnotation.class)) {
            ClassAnnotation annotation = element.getAnnotation(ClassAnnotation.class);
            MethodSpec methodSpec = MethodSpec.methodBuilder("getAuthor")
                .returns(String.class)
                .addModifiers(Modifier.PUBLIC)
                .addStatement("return " + '"' + annotation.author() + '"')
                .build();
            TypeSpec typeSpec = TypeSpec.classBuilder("My" + element.getSimpleName())
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(methodSpec)
                .build();
            JavaFile javaFile = JavaFile.builder("com.example", typeSpec)
                .addFileComment(" author by 孟召伟 \n This codes are generated automatically. Do not modify! ")
                .build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }
}

执行编译后,会在com.example下面生成一个名为MyTestAnnotaion.java文件,其中的代码为:

//  author by 孟召伟
//  This codes are generated automatically. Do not modify!
package com.example;

import java.lang.String;

public final class MyTestAnnotation {
  public String getAuthor() {
    return "Remer";
  }
}

这样我们就可以调用自动生成的方法了:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyTestAnnotation testAnnotation = new MyTestAnnotation();
        Toast.makeText(this, testAnnotation.getAuthor() , Toast.LENGTH_SHORT).show();
    }
}

参考

javapoet
auto-service
编译时注解语法

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

推荐阅读更多精彩内容