写在前面 :
该文章介绍了注解的一些基础, 我相信很多人对注解的基础已经了如执掌, 不妨接着往下看. 读完该章节你能清楚的认识到一个注解的组成部分以及如何写出想要的注解. 该章介绍了我们熟悉的运行时注解的处理方法, 因为 Retrofit 都用了. 文末有下章节预告: 有史以来最全面解析 CLASS 注解, 带你实现自己的 BufferKnufe(包看包会).
一 注解的定义
注解(Annotation), 也叫元数据. 一种代码级别的说明. 它是 JDK1.5 及以后版本引入的一个特性, 与类, 接口, 枚举是在同一个层次. 它可以声明在包, 类, 字段, 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明 . 如果要对于元数据的作用进行分类, 还没有明确的定义, 不过我们可以根据它所起的作用, 注解不会改变编译器的编译方式, 也不会改变虚拟机指令执行的顺序, 它更可以理解为是一种特殊的注释, 本身不会起到任何作用, 需要工具方法或者编译器本身读取注解的内容继而控制进行某种操作. 大致可分为三类:
编写文档: 通过代码里标识的元数据生成文档.
代码分析: 通过代码里标识的元数据对代码进行分析.
编译检查: 通过代码里标识的元数据让编译器能实现基本的编译检查.
二 用途
因为注解运行在单独的 JVM 里面, 所以我们可以使用 JVM 提供给我们的任何依赖. 另外 CLASS 和 SOURCE 类型的注解是再编译期间完成对注解的处理, 所以可以再代码编译期间帮我们完成一些复杂的准备工作. 就拿 BufferKnife 来说, 再处理注解的期间生成我们注解对象相关的 ***_ViewBinding 等类来处理 View.
三 知识准备
Java JDK 中包含了三个注解分别为 @Override(校验格式),@Deprecated(标记过时的方法或者类),@SuppressWarnnings(注解主要用于抑制编译器警告), 对于每个注解的具体使用细节这里不再论述. 我们可以通过点击这里 https://baike.baidu.com/item/Java 注解/4404368 来看一下专业解释! 来看一下 @Override 的源码.
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.SOURCE)
- public @interface Override {
- }
复制代码
通过源代码的阅读我们可以看出生命注解的方式为 @interface, 每个注解都需要不少于一个的元注解的修饰, 这里的元注解其实就是修饰注解的注解, 可以理解成最小的注解单位吧... 下面详细的看下每个注释注解的意义吧:
@Target :
说明了注解所修饰的对象范围, 也就是我们这个注解是用在那个对象上面的: 注解可被用于 packages,types(类, 接口, 枚举, 注解类型), 类型成员(方法, 构造方法, 成员变量, 枚举值), 方法参数和本地变量(如循环变量, catch 参数). 在注解类型的声明中使用了 target 可更加明晰其修饰的目标. 以下属性是多选状态, 我们可以定义多个注解作用域, 比如: (1).CONSTRUCTOR: 用于描述构造器.
(2).FIELD: 用于描述域也就是类属性之类的.
(3).LOCAL_VARIABLE: 用于描述局部变量.
(4).METHOD: 用于描述方法.
(5).PACKAGE: 用于描述包.
(6).PARAMETER: 用于描述参数.
(7).TYPE: 用于描述类, 接口(包括注解类型) 或 enum 声明.
@Target({ElementType.METHOD,ElementType.FIELD}), 单个的使用 @Target(ElementType.FIELD) .
复制代码
@Retention: 定义了该注解被保留的时间长短: 某些注解仅出现在源代码中, 而被编译器丢弃; 而另一些却被编译在 class 文件中; 编译在 class 文件中的注解可能会被虚拟机忽略, 而另一些在 class 被装载时将被读取 (请注意并不影响 class 的执行, 因为注解与 class 在使用上是被分离的). 使用这个 meta-Annotation 可以对 注解的 "生命周期" 限制. 也就是说注解处理器能处理这三类的注解, 我们通过反射的话只能处理 RUNTIME 类型的注解. 来源于 java.lang.annotation.RetentionPolicy 的枚举类型值: (1).SOURCE: 在源文件中有效(即源文件保留) 编译成 class 文件将舍弃该注解. (2).CLASS: 在 class 文件中有效(即 class 保留) 编译成 dex 文件将舍弃该注解. (3).RUNTIME: 在运行时有效(即运行时保留) 运行时可见.
@Documented: 用于描述其它类型的注解应该被作为被标注的程序成员的公共 API, 因此可以被例如 javadoc 此类的工具文档化. Documented 是一个标记注解, 没有成员.
@Inherited: 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的. 如果一个使用了 @Inherited 修饰的注解类型被用于一个 class, 则这个注解将被用于该 class 的子类. 注意:@Inherited 注解类型是被标注过的 class 的子类所继承. 类并不从它所实现的接口继承注解, 方法并不从它所重载的方法继承注解. 当被 @Inherited 类型标注的注解的 Retention 值为 RetentionPolicy.RUNTIME, 则反射 API 增强了这种继承性. 如果我们使用 java.lang.reflect 去查询一个 @Inherited 类型的注解时, 反射代码检查将展开工作: 检查 class 和其父类, 直到发现指定的注解类型被发现, 或者到达类继承结构的顶层.
@Repeatable : Repeatable 可重复性, Java1.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 的性能. 所以呢运行时注解只是大家认识注解的一个入口. 接下来我将陆续的介绍注解的通用写法.
来源: https://juejin.im/post/5b84a29ff265da4328164217