引子

作为Java / Android 开发者,我们平时开发中经常用到注解,有的方便了我们开发,动态的添加代码,减少我们的开发量;有的编译时能友好的提示我们,帮我们纠错。
现在我们就回炉,看看注解相关的问题

注解基础

Java 内置注解

Java 在jdk中,定义了一套注解,我们最常见的有:

  • @Override
  • @Deprecated
  • @SuppressWarnings

这3个就不用多说了,一看都懂。
那么,他们是分别什么时候起作用呢,这就要说说注解的保留策略了(RetentionPolicy)
我们来看看源码中是怎么定义的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}

分别是 源码级、编译时、运行时 三种策略:

RetentionPolicy 含义
SOURCE 仅存在Java源文件,经过编译器后便丢弃相应的注解
CLASS 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释
RUNTIME 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解

然后,我们也知道源码级框架是在javac编译源码时,生成框架代码或文件。源码级别框架发生过程是在编译期间,并不会过多影响到运行效率

上面提到的3个最常见的注解,前两个是源码级注解,开发过程中提示我们,后一个是运行时注解,告诉VM忽略警告

元注解

Java 中的元注解,也就是 注解的注解,有4个:

  • @Retention - 上面提到的保留策略
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 修饰对象范围:对象、方法。。。
  • @Inherited - 也是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的

我们再看看 @Target, 它的定义里,value元素是一个枚举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}

通过这个 @Target, 来标识注解作用在什么类型上

我们在定义一个注解时,就会用到元注解。像jdk提供给我们的注解,也是由他们而来的

自定义注解

在自定义个注解时,会自动继承java.lang.annotation.Annotation接口。
我们可以看到:

实现一个简单的注解demo:

1
2
3
4
5
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Setting {
String value() default "";
}

定义起来也比较简单易懂。其中有一些规则,我们要清楚:

注解参数的可支持数据类型:

  • 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  • String类型
  • Class类型
  • enum类型
  • Annotation类型
  • 以上所有类型的数组

定义一个注解的工作就完成了,那谁来读取、处理注解呢,下面就来看看注解处理器

注解处理器

JDK 提供一些相关的api, 如: AnnotatedElement,通过反射也可以获取注解相关的内容,进行一些操作

APT & AnnotationProcessor

APT(Annotation Processing Tool)是一个命令行工具,它对源代码文件进行检测找出其中的annotation后,使用annotation processors来处理annotation。
APT 也经过了几个版本的迭代,现在也不需要单独运行apt这样的命令行工具,Java编译器本身就可以完成对注解的处理。
直接继承于javax.annotation.processing.AbstractProcessor进行相应的实现。
对应Android开发者来说,apt 也被out了,Android Studio Gradle 2.2 版本以上,建议用 AnnotaitionProcessor。Gradle 也支持了annotationProcessor

自定义AnnotationProcessor

项目搭建
  1. 建立2个Java Library,分别为:setting_annotation,setting_annotation_processor
  2. setting_annotation 中定义注解,不需要添加特别的依赖库
  3. setting_annotation_processor 中定义自己的的AnnotaitionProcessor,实现下注解处理逻辑。build.gradle中需要添加:
1
2
3
implementation 'com.google.guava:guava:22.0-android'
implementation 'com.squareup:javapoet:1.9.0'// javapoet也要引用到guava
implementation 'com.google.auto.service:auto-service:1.0-rc2'

用于我们后面使用。

4.项目依赖是这样的:


贴上 具体的配置,不多说了。
app 的 gradle 配置:

1
2
implementation project(':setting_annotation')
annotationProcessor project(':setting_annotation_processor')

setting_annotation_processor的gradle配置:

1
2
3
4
implementation 'com.google.guava:guava:22.0-android'
implementation 'com.squareup:javapoet:1.9.0'// 用于自动生成java代码
implementation 'com.google.auto.service:auto-service:1.0-rc2'// 方便注解处理相关配置
implementation project(':setting_annotation')

Demo 示例

我们来实现一个 自动生成 JavaBean 的例子:

  • 注解定义(setting_annotation 中)
1
2
3
4
5
6
7
8
9
10
11
12
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Bean {
enum AttType {
INT, FLOAT, DOUBLE, STRING
}
String clzName() default "";
String[] attName() default {""};
AttType[] typeName() default {STRING};
}
  • 注解使用(app 中)
1
2
3
4
5
@Bean(clzName = "PeopleBean",
attName = {"name", "age", "sex"},
typeName = {Bean.AttType.STRING, Bean.AttType.INT, Bean.AttType.STRING})
public class BeanTest {
}
  • 注解处理(setting_annotation_processor 中)
    主要实现 AbstractProcessor 的 init()、process()、getSupportedAnnotationTypes()方法
    主要思路,在init()中拿到messager、filer相关的工具类,process()中是真正实现注解处理的地方,这里会使用到javapoet的工具类,方便操作Java代码的生成。getSupportedAnnotationTypes()描述注解处理器需要处理的注解,我们新建一个set添加我们的注解即可。
    javaopet的操作可以查阅 https://github.com/square/javapoet
    完成这些内容后,我们需要配置 processor 在META-INF/services中,我们就不麻烦了,直接用 google 提供的工具类,自动生成,也是一个注解 @AutoService 标记到类上:
1
2
@AutoService(Processor.class)
public class SettingProcessor extends AbstractProcessor
  • 验证
    我们 run 起来,首先观察,setting_annotation_processor 这个module下的产物:

META-INF下的文件自动生成了
我们再看看,app module 下:



PeopleBean 自动生成了。
OK,大功告成。

说明:注解处理器是运行它自己的虚拟机JVM中。javac启动一个完整Java虚拟机来运行注解处理器。
所以可以使用任何你在其他java应用中使用的东西。可以使用 guava 中的方便的api,处理注解操作

那么类自动生成了,那我们就可以动态的在代码中 去加载,应用了。ButterKnife 不就是这种思路吗?

如何debug注解处理器

前面介绍了大部分过程,再讲一个小技巧,debug注解处理器。
开发过程中少了debug,岂不是很麻烦了,光靠打日志比较浪费时间。Annotation Processor debug 起来还是有点特殊的。

配置远程调试

配置gradle.properties

1
2
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

开启守护进程

开始debug

  1. 执行一次 clean project(每次debug前,clean下项目)
  2. 打断点,并点击debug按钮

  3. 最后,make或者rebuild下,就可以进入 debug 状态了

注意:如果守护task 执行失败,建议重启 stuido 试试

附录

Demo地址:
https://github.com/fenglincanyi/AndroidProject/tree/master/AnnoationProcessorDemo

Android 注解库:
http://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=2649548684&idx=1&sn=e2c0b5bd10552beb6a8af59a350e268d&chksm=f1180cf1c66f85e77eaae2729da5d550af6b068143c851dd9d8e09014cf8de0d8e01472c9873&mpshare=1&scene=23&srcid=0314lppg3Lze1sjX40feRMOD%23rd

注解:
http://linbinghe.com/2017/ac8515d0.html

注解处理器:
https://medium.com/@iammert/annotation-processing-dont-repeat-yourself-generate-your-code-8425e60c6657
https://gist.github.com/iammert/c9da4150a5b5faa4b7bd8fe7915d6e6b
https://www.race604.com/annotation-processing/

javaPoet:
https://juejin.im/entry/58fefebf8d6d810058a610de
https://xsfelvis.github.io/2016/11/06/%E7%BC%96%E8%AF%91%E6%9C%9F%E6%B3%A8%E8%A7%A3%E4%B9%8BJavaPoet/

Debug annoation processor
http://www.jianshu.com/p/80a14bc35000