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

原创 2016年08月05日 13:54:15

关联内容:

java注解基础概念总结

自定义注解之运行时注解(RetentionPolicy.RUNTIME)

自定义注解之源码注解(RetentionPolicy.SOURCE)

说到编译时注解(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 工具上搜索添加,如下:


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

@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {
    // ...
}

一句话完全搞定!这时重新Make下工程也能看到同样的输出信息了。但是如果你编译生成APK时,你会发现出现错误了,如下:


发现文件重复了!这里有个解决办法是在主项目的 build.gradle 加上这么一段:

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 library
2、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')
}
这样就OK了,重新运行可以很好地工作了~

上面提到android-apt的作用有对编译时期生成的文件处理,关于生成文件的功能就不得不提 JavaPoet 了,关于这方面的内容放在下个文章里讲~

自定义注解之编译时注解(RetentionPolicy.CLASS)(二)——JavaPoet

源码:AnnotationSample

深入理解编译注解(五)RetentionPolicy.SOURCE 和 RetentionPolicy.CLASS区别讨论

前言这篇我觉得应该是一个讨论篇,因为我自己还没有找到一个非常满意的答案,希望大家一起来讨论。正文元注解RetentionPolicy,表明注解的生命周期: 1、SOURCE:在原文件中有效,被编译...
  • u011315960
  • u011315960
  • 2017年03月22日 14:19
  • 2015

java自定义注解

java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。 注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.anno...
  • yixiaogang109
  • yixiaogang109
  • 2012年03月07日 14:01
  • 36526

JavaAPI之注释类型Retention

结构 java.lang.annotation 注释类型 Retention @Documented @Retention(value=RUNTIME) @Target(value=ANNOTA...
  • u010142437
  • u010142437
  • 2016年06月05日 23:40
  • 579

JAVA自定义注释(Target,Retention,Documented,Inherit)

定义自己的注释类型 通过添加了一个小小的语法(Tiger 添加了大量的语法结构),Java 语言支持一种新的类型 —— 注释类型(annotation type) 。注释类型看起来很像普通的类,但...
  • hbzyaxiu520
  • hbzyaxiu520
  • 2011年02月28日 13:37
  • 11535

自定义注解之运行时注解(RetentionPolicy.RUNTIME)

对注解概念不了解的可以先看这个:Java注解基础概念总结 前面有提到注解按生命周期来划分可分为3类: 1、RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成clas...
  • github_35180164
  • github_35180164
  • 2016年08月04日 15:05
  • 11095

自定义注解之源码注解(RetentionPolicy.SOURCE)

源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,j...
  • github_35180164
  • github_35180164
  • 2016年08月04日 16:45
  • 3526

Java注解之Retention、Documented、Inherited介绍

出处:http://www.jb51.net/article/55371.htm 这篇文章主要介绍了Java注解之Retention、Documented、Inherited注解介绍,本文内容和相关...
  • aayygg1234
  • aayygg1234
  • 2015年05月18日 14:44
  • 357

JAVA自定义注释(Target,Retention,Documented,Inherit)

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。  注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.ann...
  • nkwangjie
  • nkwangjie
  • 2015年10月09日 15:34
  • 1563

Android关于AutoService、Javapoet讲解

AutoService会自动在META-INF文件夹下生成Processor配置信息文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/s...
  • hqiangtai
  • hqiangtai
  • 2017年12月21日 17:39
  • 100

Android Model正确使用姿势——AutoValue

Android Model正确使用姿势——AutoValueAndroid Model正确使用姿势AutoValue 前言 简介 简单使用 ImmutableValue types 高级使用 Null...
  • xia215266092
  • xia215266092
  • 2016年12月05日 14:30
  • 4593
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:自定义注解之编译时注解(RetentionPolicy.CLASS)(一)
举报原因:
原因补充:

(最多只允许输入30个字)