关于注解我知道的知识

110 阅读4分钟

怎么理解注解

  1. 注解就像是一个 数字化的商品标签, 实物的商品标签由扫码枪识别成二进制数据, 交由解析库根据一定规则解析后, 保存到对应的数据结构中(例如 url=www.baidu.com 这样的数据).

  2. java 注解的实物是填充了数据后, 附着于类的某个部分的注解对象, 但是该对象不是通过 new 的方式显示生成的, 是由编译器自动生成的(我自己这么理解的, 具体注解实例的生成时机, 存户位置我不知道).

  3. 注解可附着的位置: 编写注解时, 通过 @Target({ElementType.TYPE, ElementType.FIELD, ...}) 指定.

PS: @Target 作用于 @interface 定义的类上. 本身是 ElementType.ANNOTATION_TYPE 类型

  1. 注解的扫描者们, 例如 (IDE/JAVAC, ASM/AspectJ, 反射机制), 会根据规则从 源码(.java)/字节码(.class)中解析出注解, 并根据自己对注解对象中的数据的定义, 完成注解标签的解释(比如二维码解析出 map 数据结构, 怎么通过 map 结构里的数据得出结论就是上层 app 自己的事了).
求教:
注解的扫描者具体是谁呢???
.class文件以及运行时, 注解的存储形式和位置是怎么样的呢?
  1. 因为最终可以生成注解对象, 因此可以通过注解对象中的方法, 获取生成注解时填充的值. 由于目前我了解到的注解都是附着于类的某个部分, 因此该值在编译期间在源码里就已经写死并且确定, 因此无法在运行期间修改.
自定义注解的例子, 以及注解的范围的示例:
// 注解 @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() {
    }
}

注解的生存周期

  1. RetentionPolicy.SOURCE 源代码级别 =》对应 APT (Annotation Process Tool)
  2. RetentionPolicy.CLASS 字节码级别 =》对应 ASM/AspectJ 等字节码处理框架
  3. RetentionPolicy.RUNTIME 运行时级别 =》对应 Class 信息

注解生命周期的定义方式

// 注意这里的注解 @Retentation 指定了该注解的留存周期.
@Retentation(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}

注解的使用者

  1. IDE 或者 IDE插件: 通过对 java 类的解析, 识别出对应的注解, 通过注解中规定的规则, 校验当前注解所作用的对象(类/方法/属性/注解), 是否满足规则. (例如 @IntDef({0, 1}) 限制了当前注解作用的对象取值只能为 0 和 1. 例如 @Override 限制了当前方法必须重写自付类. 这些皆是由 IDE 来使用的注解的例子.)
  1. JAVAC 或 ASM/AspectJ 等框架

2.1 javac 在执行过程中, 会将所有的注解信息, 传递给通过 -classpath 指定的 AbstractProcessor 的实现类, 从而完成注解的解析. (有时候我们可以通过该方式自定义注解解析库, 生成辅助代码. BufferKnife 是不是这个原理呢 ?) 2.2 AspectJ 的安卓插件, 该插件由 gradle 打包过程中识别到对应的处理器, 将注解信息发送给 AspectJ 的处理器. (通过 PointCut 匹配切点, 通过 JointPoint 完成改造. AspectJ 将字节码操作抽象成在指定位置插入代码的概念, 屏蔽了字节码插入的具体操作. 由于有额外的代码封装, 略微影响性能.)

  1. 通过 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);
    }

注解的威力

  1. ARouter: 通过注解生成路由表. (不知道是不是通过 AspectJ 在 gradle 打包过程中生成的.)

  2. Retrofit: 通过注解识别参数对应的数据(通过动态代理 + 注解解析, 获取保存在注解中的请求参数).