Java 注解

最后更新: 2021-01-10 11:11:46 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

注解是附加到声明或类型使用位置的元数据。它不是通用的对象字面量:注解类型是一种特殊接口,其成员描述允许使用的元数据。

声明注解类型 #

注解成员没有参数或类型参数,也不能声明 throws 子句。成员类型仅限于基本类型、String、Class、枚举类型、注解类型,或上述类型之一的一维数组。值必须是编译时常量、类字面量、枚举常量、注解或它们的数组。null 永远不能作为注解元素值。

java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Retry {
    int attempts() default 3;
    Class<? extends Throwable>[] retryOn() default {};
}

@Target 限制注解可以出现的位置;相关位置包括声明、类型使用位置、类型参数、模块和记录组件。如果缺少该元注解,注解可以写在任意声明上下文中,但不能写在类型上下文中。@Retention 控制编译器是否丢弃注解(SOURCE)、将其记录到类文件中(CLASS,默认值),或将其记录下来供反射使用(RUNTIME)。@Inherited 只影响针对 Class 的查询、类上的注解,以及沿超类链进行的查询。它不会从接口、方法、字段、参数或类型使用位置继承注解。

默认值会被编译到注解类型中,而不是复制到每个使用位置。因此,当旧客户端类文件与新的注解类型一起运行时,修改默认值可能改变客户端观察到的结果。

反射与访问边界 #

Class、Method、Field、Constructor、Parameter、Package 和 Module 都实现了 AnnotatedElement。getDeclared* 方法检查直接存在于该元素上的注解。非 getDeclared 方法可能包含继承的类注解;getAnnotationsByType 和 getDeclaredAnnotationsByType 还会展开可重复注解的容器。

java
Retry[] policies = operation.getAnnotationsByType(Retry.class);

运行时保留会使元数据可见;它不会授予对成员的访问权限,也不会使公开私有状态变得安全。反射仍必须通过 Java 语言访问检查;对于命名模块,还必须通过强封装检查。只有当包已向调用方开放时,trySetAccessible 才能抑制适用的语言访问检查;命令行 --add-opens 是部署决策,而不是库契约。传统的 SecurityManager 已被弃用并计划移除,因此安全设计应使用模块边界、进程权限、输入验证和显式 API,而不应假定存在全局反射策略。

不应仅使用检查操作作为初始化机制。反射式构造和调用可能初始化类,而访问注解成员可能要求解析已链接的类型。应使静态初始化不依赖必需配置,也不产生对外可见的副作用。

可重复注解 #

@Repeatable 指定一个容器注解,该容器唯一的必需成员是重复注解类型的数组。容器的保留策略至少必须与重复注解类型一样长,并且必须具有兼容的目标。

java
@Repeatable(Labels.class)
@interface Label { String value(); }

@interface Labels { Label[] value(); }

@Label("read") @Label("write")
final class Permission {}

对于多个出现位置,语言会将声明建模为一个隐式声明的容器;因此,类文件使用者看到的容器形式与显式的 @Labels({ ... }) 声明相同。单个 @Label 通常不会与单元素的显式容器逐字节等价。getAnnotation(Label.class) 不会展开容器,而 getAnnotationsByType(Label.class) 会展开。

类文件与代理实例 #

运行时可见的声明注解存储在类文件属性中,例如 RuntimeVisibleAnnotations;参数注解和类型使用注解则存储在独立属性中。编译器会验证注解语法并编码元素值。VM 验证类文件结构,而核心反射会按需解析这些属性,并通过 AnnotatedElement 暴露结果。

反射实现会将返回的运行时注解表示为实现该注解接口的对象;在 OpenJDK 中,通常是动态代理。客户端必须将这种表示视为未指定实现。应依赖 annotationType()、声明的成员方法,以及 Annotation 的相等性、哈希码和字符串契约,而不要依赖代理类或对象身份。

参考资料 #

评论

在 GitHub 上阅读或评论(发表评论需要登录)