JDK中的元注解

66 阅读4分钟

在 Java 体系中(截止到 17 ),元注解只有 5 个

JDK中的5个元注解

@Target

指定一个注解的使用范围,表示被描述的注解可以用在什么地方,想要让一个注解可以被使用,就必须使用 @Target 来标注它的使用范围

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

ElementType 是一个枚举类

其中列举了可以使用注解的元素类型:

名称说明
TYPE用于类/接口/枚举
FIELD用于成员变量(包含枚举常量)
METHOD用于方法
PARAMETER用于形式参数
CONSTRUCTOR用于构造函数
LOCAL_VARIABLE用于局部变量
ANNOTATION_TYPE用于注解类型
PACKAGE用于包
TYPE_PARAMETER用于类型参数(jdk8新增)
TYPE_USE用于类型使用(jdk8新增)
MODULE用于模块(jdk9新增)

@Documented

会被 JavaDoc 工具提取成文档

javadoc -c mydoc xxx.java

如果类里面使用的注解上没有 @Documented 那么生成的文档也不会。如果有,生成的文档也会保留。

@Retention

用于描述注解的保留策略,表示在什么级别保存该注解信息

注意:如果我们定义的一个注解需要在运行期间通过反射读取,那么就需要把 RetentionPolicy 设置成 RUNTIME。

@Inherited

指定该注解可以被继承

它只会影响类上面的注解,而方法和属性等上面的注解的继承性是不受 @Inherited 影响的

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
名称说明
SOURCE注解会被编译器丢弃,即只在原文件中保留
CLASS编译器将注解记录在 Class 文件中,但不需要在运行时由虚拟机保留。这是所有注解默认保留策略
RUNTIME编译器将注解记录在 Class 文件中,并在运行时由虚拟机保留,因此可以以反射方式读取他们

@Repeatable

是 Java8 新增的一个元注解,使用该注解来标识允许一个注解在一个元素上使用多次

注解的继承与组合

注解类是不能继承其他类也不能实现其他接口的。但是,注解之间是可以建立组合关系的

类似于 @SpringBootApplication 注解,就是多个注解的组合;

注解的组合层数没有限制,可以无限组合;但是当我们通过反射获取一个类的注解时,只能获取组合注解,无法获取被组合的注解,需要通过组合注解的二次解析才能得到

如果使用 Spring 那么直接使用 Spring 中的 AnnontatedElementUtils 的 getMergedAnnotation 方法可以获取被组合的注解。

日常开发中常用的注解

  • 使用自定义注解做日志记录
  • 使用自定义注解,在接口方法做缓存
  • 参数校验

等等

不要过度依赖注解

注解使用比较简单,但是其部分内部逻辑很容易被忽略

比如:

在 Spring 体系中,关于事物的管理有两种模式,分别是编程式事务和声明式事务,其中声明式事务就是注解实现,但是不建议过度依赖声明式事务

编程式事务

基于底层的 API ,如 PlatformTransactionManager/TransactionDefinition/TransactionTemplate等核心接口,开发者完全可以通过编程的方式进行事务的管理

需要开发者在代码中手动管理事务的开启/提交/和回滚等操作,可以通过Api自己控制事务

public void test(){
    TransactionDefinition def=new DefaultTransactionDefinition();
    TransactionStatus status=transactionManager.getTransaction(def);
    try{
        //事务操作
        //事务提交
        transactionManager.commit(status);
    }catch(Exception e){
    //事务回滚
        transactionManager.rollback(status);
        throw e;
    }
}

声明式事务

允许开发者在配置的帮助下来管理事务,而不需要依赖底层API进行硬编码

比如 @Transactional 注解

它会自动进行事务的开启/提交/回滚等操作,使用 AOP 实现的,本质上就是在目标方法执行前后进行拦截,使用这种方式,对代码没有侵入性,在方法内只需要编写业务逻辑即可

声明式事务粒度问题

声明式事务的最小粒度要作用在方法上。

如果一个方法是被事务嵌套的,那么就可能在方法中加入一些如 RPC 远程调用/消息发送/缓存更新和文件写入等操作。

声明式事务失效场景

  1. 修饰在非 public 方法上
  2. 注解属性 propagation 设置错误
  3. 注解属性rollbackFor 设置错误
  4. 同一个类中调用方法
  5. 异常被 catch 捕获
  6. 数据库引擎不支持