一 注解的定义
注解(Annotation), 也叫元数据. 一种代码级别的说明. 它是 JDK1.5 及以后版本引入的一个特性, 与类, 接口, 枚举是在同一个层次. 它可以声明在包, 类, 字段, 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明 . 如果要对于元数据的作用进行分类, 还没有明确的定义, 不过我们可以根据它所起的作用, 注解不会改变编译器的编译方式, 也不会改变虚拟机指令执行的顺序, 它更可以理解为是一种特殊的注释, 本身不会起到任何作用, 需要工具方法或者编译器本身读取注解的内容继而控制进行某种操作. 大致可分为三类:
编写文档: 通过代码里标识的元数据生成文档.
代码分析: 通过代码里标识的元数据对代码进行分析.
编译检查: 通过代码里标识的元数据让编译器能实现基本的编译检查.
二 用途
因为注解可以再代码编译期间帮我们完成一些复杂的准备工作, 所以我们可以利用注解去完成我们的一些准备工作. 比如 Greendao, 我们注解一个实体类, 它要处理成好多逻辑关系类, 这些逻辑类让我们自己去书写的话那将是一个庞大的代码量, extends AbstractDao 等这些类. 比如 BufferKnife, 我们用注解将控件的属性传递给它, 它将生成一些功能类去处理这些值,***_ViewBinding 等这些类型的类. 运行时注解的使用, 比如 Retrofit 的 @GET 或者 @POST 等等都是运行时注解, 它的注解的处理必须跟 Retrofit 的对象有关联, 所以必须定义成运行时的. 所以注解已经成为一种趋势, 比如 BufferKnife,EventBus,Darrger,Greendao,Arouter,Retrofit... 看来我们也要去完成我们的一个注解了.
三 知识准备
Java JDK 中包含了三个注解分别为 @Override(校验格式),@Deprecated:(标记过时的方法或者类),@SuppressWarnnings(注解主要用于抑制编译器警告)等等 java1.8 之后有新增了一些注解像 @FunctionalInterface()这样的, 对于每个注解的具体使用细节这里不再论述. 我们可以通过点击这里来看一下专业解释! 来看一下 @Override 的源码.
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- }
通过源代码的阅读我们可以看出生命注解的方式为 @interface, 每个注解都需要不少于一个的元注解的修饰, 这里的元注解其实就是修饰注解的注解, 可以理解成最小的注解单位吧... 下面详细的看下每个注释注解的意义吧:
@Target: 说明了 Annotation 所修饰的对象范围, 也就是我们这个注解是用在那个对象上面的: Annotation 可被用于 packages,types(类, 接口, 枚举, Annotation 类型), 类型成员(方法, 构造方法, 成员变量, 枚举值), 方法参数和本地变量(如循环变量, catch 参数). 在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标. 以下属性是多选状态, 我们可以定义多个注解作用域, 比如:
@Target({ElementType.METHOD,ElementType.FIELD}), 单个的使用 @Target(ElementType.FIELD).
(1).CONSTRUCTOR: 构造方法声明.
(2).FIELD: 用于描述域也就是类属性之类的, 字段声明(包括枚举常量).
(3).LOCAL_VARIABLE: 用于描述局部变量.
(4).METHOD: 用于描述方法.
(5).PACKAGE: 包声明.
(6).PARAMETER: 参数声明.
(7).TYPE: 类, 接口 (包括注释类型) 或枚举声明 .
(8) .ANNOTATION_TYPE: 注释类型声明, 只能用于注释注解.
官方解释: 指示注释类型所适用的程序元素的种类. 如果注释类型声明中不存在 Target 元注释, 则声明的类型可以用在任一程序元素上. 如果存在这样的元注释, 则编译器强制实施指定的使用限制. 例如, 此元注释指示该声明类型是其自身, 即元注释类型. 它只能用在注释类型声明上:
- @Target(ElementType.ANNOTATION_TYPE)
- public @interface MetaAnnotationType {
- }
此元注释指示该声明类型只可作为复杂注释类型声明中的成员类型使用. 它不能直接用于注释:
- @Target({})
- public @interface MemberType {
- ...
- }
这是一个编译时错误, 它表明一个 ElementType 常量在 Target 注释中出现了不只一次. 例如, 以下元注释是非法的:
- @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
- public @interface Bogus {
- ...
- }
@Retention: 定义了该 Annotation 被保留的时间长短: 某些 Annotation 仅出现在源代码中, 而被编译器丢弃; 而另一些却被编译在 class 文件中; 编译在 class 文件中的 Annotation 可能会被虚拟机忽略, 而另一些在 class 被装载时将被读取 (请注意并不影响 class 的执行, 因为 Annotation 与 class 在使用上是被分离的). 使用这个 meta-Annotation 可以对 Annotation 的 "生命周期" 限制. 来源于 java.lang.annotation.RetentionPolicy 的枚举类型值: (1).SOURCE: 在源文件中有效(即源文件保留) 编译成 class 文件将舍弃该注解. (2).CLASS: 在 class 文件中有效(即 class 保留) 编译成 dex 文件将舍弃该注解. (3).RUNTIME: 在运行时有效(即运行时保留) 运行时可见. 也就是说注解处理器能处理这三类的注解, 我们通过反射的话只能处理 RUNTIME 类型的注解.
官方解释: 指示注释类型的注释要保留多久. 如果注释类型声明中不存在 Retention 注释, 则保留策略默认为 RetentionPolicy.CLASS. 只有元注释类型直接用于注释时, Target 元注释才有效. 如果元注释类型用作另一种注释类型的成员, 则无效.
@Documented: 指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化. 应使用此类型来注释这些类型的声明: 其注释会影响由其客户端注释的元素的使用. 如果类型声明是用 Documented 来注释的, 则其注释将成为注释元素的公共 API 的一部. Documented 是一个标记注解, 没有成员.
@Inherited: 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的. 如果一个使用了 @Inherited 修饰的 annotation 类型被用于一个 class, 则这个 annotation 将被用于该 class 的子类. 注意:@Inherited annotation 类型是被标注过的 class 的子类所继承. 类并不从它所实现的接口继承 annotation, 方法并不从它所重载的方法继承 annotation. 当 @Inherited annotation 类型标注的 annotation 的 Retention 是 RetentionPolicy.RUNTIME, 则反射 API 增强了这种继承性. 如果我们使用 java.lang.reflect 去查询一个 @Inherited annotation 类型的 annotation 时, 反射代码检查将展开工作: 检查 class 和其父类, 直到发现指定的 annotation 类型被发现, 或者到达类继承结构的顶层.
官方解释: 指示注释类型被自动继承. 如果在注释类型声明中存在 Inherited 元注释, 并且用户在某一类声明中查询该注释类型, 同时该类声明中没有此类型的注释, 则将在该类的超类中自动查询该注释类型. 此过程会重复进行, 直到找到此类型的注释或到达了该类层次结构的顶层 (Object) 为止. 如果没有超类具有该类型的注释, 则查询将指示当前类没有这样的注释. 注意, 如果使用注释类型注释类以外的任何事物, 此元注释类型都是无效的. 还要注意, 此元注释仅促成从超类继承注释; 对已实现接口的注释无效.
@Repeatable: Repeatable 可重复性, Java 1.8 新特性, 其实就是把标注的注解放到该元注解所属的注解容器里面. 可重复性的意思还是用 demo 来解释一下吧:
- // 定义了一个 注解里面属性的返回值是其他注解的数组
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyCar {
MyTag[] value(); ----MyTag 这里就是 MyTag 注解的容器.
- }
- // 另外一个注解 就是上一个注解返回的注解
- @Target({ElementType.METHOD,ElementType.FIELD})
- @Retention(RetentionPolicy.CLASS)
@Repeatable(MyCar.class) -------- 这里添加这个属性之后 我们的这个注解就可以重复的添加到我们定义的容器中了, 注意里面的值时 我们定义的容器注解的 class 对象.
- public @interface MyTag { ........MyTag
- String name () default "" ;
- int size () default 0 ;
- }
- // 使用
- @MyTag(name = "BWM", size = 100)
- @MyTag()
- public Car car;
- // 如果我们的注解没有 @Repeatable 的话, 这样写的话是报错的, 加上之后就是这样的了
这个注解是很特殊的, 我们的注解中有 @Repeatable(MyCar.class)这样的元注解的话, 就是说当前标注的注解 (MyTag 注解) 放到我们的值 (MyCar.class) 这个注解容器里面. 那么我们再处理注解的时候获取到的是我们最后的注解容器(MyCar 注解), 这样说有点生硬下面看 demo:
使用:
- public class HomeActivity extends AppCompatActivity {
- @MyTag(name = "BWM", size = 100)
@MyTag(name = "大众" ,size = 200) ...... 这里用了它的重复性.
- Car car;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_home);
- AnnotationProccessor.instance().inject(this); // 这里去处理注解
- // Log.e("WANG", "HomeActivity.onCreate." + car.toString());
- }
- }
处理过程:
- Class<?> aClass = o.getClass();
- Field[] declaredFields = aClass.getDeclaredFields();
- for (Field field:declaredFields) {
- if(field.getName().equals("car")){
- Annotation[] annotations = field.getAnnotations();
- for (int i = 0; i <annotations.length; i++) {
- Annotation annotation = annotations[i];
- // 我们获取的该字段上面的注解只有一个 那就是 MyCar 注解, 看结果 1 的打印.
- // 但是我们明明标注的是 MyTag. 为什么获取的是注解容器呢.
- // 这就是 @Repeatable 的强大之处.
- Class<? extends Annotation> aClass1 = annotation.annotationType();
- Log.e("WANG","AnnotationProccessor.MyCar"+aClass1 );
- }
- MyCar annotation = field.getAnnotation(MyCar.class);
- MyTag[] value = annotation.value();
- for (int i = 0; i <value.length; i++) {
- MyTag myTag = value[i];
- Log.e("WANG","AnnotationProccessor.MyTag name value is"+myTag.name() );
- }
- }
结果是:
- AnnotationProccessor.MyCarinterface cn.example.wang.routerdemo.annotation.MyCar.1
- AnnotationProccessor.MyTag name value is BWM.2
AnnotationProccessor.MyTag name value is 大众. 3
三 自定义运行时注解
通过以上的学习我们知道 @interface 是声明注解的关键字, 每个注解需要注明生命周期以及作用范围. 你可以给注解定义值. 也就是再注解内部定义我们需要的方法. 这样注解就可以再自己的生命周期内为我们做事. 这里我们就自定义一个为一个对象属性初始化的注解吧, 类似于 Dagger 的功能.
- public @interface MyTag {
- }
注解里面的定义也是有规定的:
注解方法不能带有参数.
注解方法返回值类型限定为: 基本类型, String,Enums,Annotation 或者这些类型的数组.
注解方法可以有默认值.
注解本身能够包含元注解, 元注解被用来注解其他注解.
我们就来试一下吧!
- public @interface MyTag {
- // 声明返回值类型, 这里可没有大括号啊, 可以设置默认返回值, 然后就直接 ";" 了啊.
- String name () default "" ;
- int size () default 0 ;
- }
定义好了注解我们就来规定我们自定义的注解要在哪里用? 要何时用? 因为我们这里使用了反射来处理注解, 反射就是在代码的运行的时候通过 class 对象反相的去获取类内部的东西, 不熟悉反射机制的请移步这里 Android 开发者必须了解的反射基础, 所以我们定义该注解的生命周期在运行时, 并且该注解的的目的是为自定义属性赋值, 那么我们的作用域就是 FIELD. 这里面定义了我们要初始化的 bean 的基本属性, 给了默认值. 这样我们就可以用该注解去创建我们需要的 bean 对象.
- @Target(ElementType.FIELD)
- @Inherited
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyTag {
- String name () default "" ;
- int size () default 0 ;
- }
好了接下来看怎么使用我们的这个自定义的注解!
- public class HomeActivity extends AppCompatActivity {
- @MyTag(name = "BMW",size = 100)
- Car car;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_home);
- // 这里我们要首先注册一下这个类
- AnnotationCar.instance().inject(this);
- // 当程序运行的时候这里将会输出该类 Car 的属性值.
- Log.e("WANG","Car is"+car.toString());
- }
- }
注解如果没有注解处理器, 那么该注解将毫无意义. 这里呢我们在这个 Activity 里面定义了一个 Car 类的属性, 然后再 car 这个变量上面定义我们的注解, 并且给我们的注解赋值. 然后我们再 onCreate 方法里面初始化我们的注解处理器. 然后运行代码, log 日志将打印 Car 类的信息, 先来看下结果吧
cn.example.wang.routerdemo E/WANG: Car is Car [name=BMW, size=100]
这样我们的自定义注解就有作用了, 下面是 "注解处理器" 的代码, 这里都是我们自己编写的处理注解的代码, 其实系统是自带注解处理器的, 不过它一般用来处理源码注释和编译时注释.
- // 自定义的类
- /**
- * Created by WANG on 17/11/21.
- */
- public class AnnotationCar {
- private static AnnotationCar annotationCar;
- public static AnnotationCar instance(){
- synchronized (AnnotationCar.class){
- if(annotationCar == null){
- annotationCar = new AnnotationCar();
- }
- return annotationCar;
- }
- }
- public void inject(Object o){
- Class<?> aClass = o.getClass();
- Field[] declaredFields = aClass.getDeclaredFields();
- for (Field field:declaredFields) {
- if(field.getName().equals("car") && field.isAnnotationPresent(MyTag.class)) {
- MyTag annotation = field.getAnnotation(MyTag.class);
- Class<?> type = field.getType();
- if(Car.class.equals(type)) {
- try {
- field.setAccessible(true);
- field.set(o, new Car(annotation.name(), annotation.size()));
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
- }
这就说明了为什么注解和反射是同时进入我们的知识圈里面的吧! 这里呢我们先获取到类里面所有的属性, 然后去找到被我们的注解 MyTag 修饰的那个属性, 然后找到之后, 先取我们注解里面的值, 然后赋值给我们类里面的属性! 这样我们就用注解去初始化了一个属性值. 就是这么简单!
四 总结
运行时注解是我们比较好理解的, 知道反射和注解基础之后就可以写出来个小 demo 了. 但是运行时注解是是我们最不常用的注解, 因为反射再运行时的操作是十分的耗时的, 我们不会因为一些代码的简洁而影响 app 的性能. 所以呢运行时注解只是大家认识注解的一个入口, 接下来我将陆续的介绍注解的通用写法, CLASS 注解和 SOURCE 注解, 让我们来完成属于自己的 BufferKnife!
Demo 地址, 欢迎 start https://github.com/WangcWj/CustomAnnotationDemo
来源: https://juejin.im/entry/5b8e1b39e51d45389005cda5