怎么理解注解
注解就像是一个 数字化的商品标签, 实物的商品标签由扫码枪识别成二进制数据, 交由解析库根据一定规则解析后, 保存到对应的数据结构中(例如 url=www.baidu.com 这样的数据).
java 注解的实物是填充了数据后, 附着于类的某个部分的注解对象, 但是该对象不是通过 new 的方式显示生成的, 是由编译器自动生成的(我自己这么理解的, 具体注解实例的生成时机, 存户位置我不知道).
注解可附着的位置: 编写注解时, 通过 @Target({ElementType.TYPE, ElementType.FIELD, ...}) 指定.
PS: @Target 作用于 @interface 定义的类上. 本身是 ElementType.ANNOTATION_TYPE 类型
- 注解的扫描者们, 例如 (IDE/JAVAC, ASM/AspectJ, 反射机制), 会根据规则从 源码(.java)/字节码(.class)中解析出注解, 并根据自己对注解对象中的数据的定义, 完成注解标签的解释(比如二维码解析出 map 数据结构, 怎么通过 map 结构里的数据得出结论就是上层 app 自己的事了).
求教:
注解的扫描者具体是谁呢???
.class文件以及运行时, 注解的存储形式和位置是怎么样的呢?
- 因为最终可以生成注解对象, 因此可以通过注解对象中的方法, 获取生成注解时填充的值. 由于目前我了解到的注解都是附着于类的某个部分, 因此该值在编译期间在源码里就已经写死并且确定, 因此无法在运行期间修改.
自定义注解的例子, 以及注解的范围的示例:
// 注解 @MyAnnotation 只可以作用于函数的参数
@java.lang.annotation.Target(ElementType.PARAMETER)
public @interface MyAnnotation {
int value();
String id();
}
public enum ElementType {
TYPE, // 类
FIELD, // 属性
METHOD, // 方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造器
LOCAL_VARIABLE, // 函数局部变量(这里不确定)
ANNOTATION_TYPE,// 注解
PACKAGE, // ???
TYPE_PARAMETER, // ???
TYPE_USE, // ???
MODULE; // ???
private ElementType() {
}
}
注解的生存周期
- RetentionPolicy.SOURCE 源代码级别 =》对应 APT (Annotation Process Tool)
- RetentionPolicy.CLASS 字节码级别 =》对应 ASM/AspectJ 等字节码处理框架
- RetentionPolicy.RUNTIME 运行时级别 =》对应 Class 信息
注解生命周期的定义方式
// 注意这里的注解 @Retentation 指定了该注解的留存周期.
@Retentation(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}
注解的使用者
- IDE 或者 IDE插件: 通过对 java 类的解析, 识别出对应的注解, 通过注解中规定的规则, 校验当前注解所作用的对象(类/方法/属性/注解), 是否满足规则. (例如 @IntDef({0, 1}) 限制了当前注解作用的对象取值只能为 0 和 1. 例如 @Override 限制了当前方法必须重写自付类. 这些皆是由 IDE 来使用的注解的例子.)
- JAVAC 或 ASM/AspectJ 等框架
2.1 javac 在执行过程中, 会将所有的注解信息, 传递给通过 -classpath 指定的 AbstractProcessor 的实现类, 从而完成注解的解析. (有时候我们可以通过该方式自定义注解解析库, 生成辅助代码. BufferKnife 是不是这个原理呢 ?) 2.2 AspectJ 的安卓插件, 该插件由 gradle 打包过程中识别到对应的处理器, 将注解信息发送给 AspectJ 的处理器. (通过 PointCut 匹配切点, 通过 JointPoint 完成改造. AspectJ 将字节码操作抽象成在指定位置插入代码的概念, 屏蔽了字节码插入的具体操作. 由于有额外的代码封装, 略微影响性能.)
- 通过 Class 在程序运行期间动态取到的注解信息: Class.getGenericSuperClass().getActualTypeArguments()
自定义注解的一个例子
public class MyActivity extends Activity {
@java.lang.annotation.Target(ElementType.PARAMETER)
// @IntDef 注解由 androidx support 包提供
// PS: 设置了 MyAnnotation 附着的函数参数, 取值范围为 0 或者 1
// 该规则由 IDE 解析, 数据结构中不过是 MyAnnotation 有个 @IntDef 注解
// 该 @IntDef 会通过 value 属性返回一个数组, 数组中包含0和1两个值
@androidx.annotation.IntDef({0, 1})
public @interface MyAnnotation {
int value();
String id();
}
private void setData(@MyAnnotation(value = 1, id = "id") int data) {
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Error:
// Must be one of: 0, 1
setData(2);
}
注解的威力
ARouter: 通过注解生成路由表. (不知道是不是通过 AspectJ 在 gradle 打包过程中生成的.)
Retrofit: 通过注解识别参数对应的数据(通过动态代理 + 注解解析, 获取保存在注解中的请求参数).