聊聊 Java 注解(上)

字数 1480阅读 291

Java 语言提供了一个很好的特性 Annotation, 中文常翻译为注解,在平时的编程中也比较常用,最明显的好处就是让代码变得更简洁明了。比如,标记一个函数是否重写了父类方法或者实现了接口的抽象方法,用 @Override 在方法上注解;声明一段函数显示地执行开启事务操作用 @Transaction 注解。

但是注解本身并不是程序的一部分,它并不会对代码的执行有直接的影响,比如把 @Override 注解去掉,重写还有效的,重写本身并不是受 Override 控制的。而像 @Transaction 这种注解去掉后,AutoCommit的设置就会是 true,每条 SQL 就会作为一个事务自动提交。似乎逻辑受影响了?其实不然,而是我们利用了 Spring 的 AOP 的机制,扫描到有 @Transaction 注解的代码,加了一些开启事务、提交事务的逻辑,这个逻辑是在单独的一块代码中实现的,我们只是通过注解来识别了需要加这个逻辑的代码片段。

所以,注解本质上是一种程序的标记行为。

基本概念

Java注解用 @ 符号这种语法形式标记,它由两个基本部分组成:

  1. 名字 ( name )
  2. 元素 ( element )

注解的名字就是一个注解的唯一标识(严格说包含上包名,不同的包下注解名可以重复,但仍是不同的注解),比如 @Override 的名字就是 Override (具体来说是java.lang.Override)。 注解的元素是注解提供一种辅助信息的手段,类似于对象的属性,比

@Qualifier(value = "userService”)

value 就是 Qualifier 的一个元素,这里 value 的值为 userService , 当只有一个元素的时候,元素的名字可以省略:

@Qualifier("userService”)

而当元素有默认值的时候,可以直接省略元素赋值而使用默认值,比如 @Autowired 注解其实有一个默认值为 true 的元素 required. 当然,注解也可以没有任何元素,比如 @Override .

了解了上面的基本概念,基本上就可以轻松地使用注解了,比如我们经常会用 @Autowired 注解标明一个对象的属性值自动从 IoC 容器中获得。但是,只会使用 JDK 或者第三方提供好的注解还不行,我们还需要学会自定义注解。

声明注解

我们以一个 Spring 的 @Autowired 的例子来说明,如何声明一个注解, 请看 Spring 的 @Autowired 源码:

package org.springframework.beans.factory.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

从例子可以看出,声明一个自定义注解的时候,用 @interface 来标注(没错,很像接口,但不是接口),有 public 这种指明可访问范围的修饰符,有注解的名字 Autowired,有元素声明,同时可以用 default 关键字来设置元素的默认值。

除了名字、元素声明之外,声明一个注解的时候还需要在这个注解之上再加上注解,比如上面例子中的 TargetRetentionDocumented. 这引出了下面一个重要的概念:元注解。

元注解

简单地说,元注解( meta-annotation )就是注解的注解,普通的注解可以修饰类、方法等,而元注解可以修饰注解,甚至包括元注解本身。一个注解是不是元注解的标志为加有 @Target 注解,并且元素 ElementType 包括值ElementType.ANNOTATION_TYPE。

比如 @Documented 是一种元注解,因为被 @Target 标记并且类型为 ANNOTATION_TYPE, 被 @Documented 标记的注解可以被 javadoc 工具识别,而 @Documented 本身也可以被 @Documented 标记:

package java.lang.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

Java 标准库中定义的元注解有 5 个:

  • @Retention : 表明注解的保留策略,有三个基本的保留策略
    • RetentionPolicy.SOURCE 源码级别,编译器在编译时会把这类注解剔除
    • RetentionPolicy.CLASS class文件级别,编译时会保留在class文件中,但是 JVM 在执行class文件是忽略这类注解
    • RetentionPolicy.RUNTIME 运行期级别,JVM 会保留这类注解,在运行时能使用这类注解
  • @Documented :被 javadoc 工具识别来生成相关文档
  • @Target : 指明注解作用的 Java 元素
    • ElementType.TYPE

    • ElementType.FIELD

    • ElementType.METHOD

    • ElementType.PARAMETER

    • ElementType.CONSTRUCTOR

    • ElementType.LOCAL_VARIABLE

    • ElementType.ANNOTATION_TYPE

    • ElementType.PACKAGE

    • ElementType.TYPE_PARAMETER

    • ElementType.TYPE_USE

      其中 TYPE_PARAMETER 和 TYPE_USE 都是 Java 8 之后才支持的,即在 Java 8 之前注解只能作用在声明语句上(比如方法声明、类声明),而 Java 8 之后可以作用在类型使用过程中,比如
      str = (@NonNull String) tmp;这种强制类型装换中出现的 @NonNull 注解可以作用在String类型之前,这种语法在 Java 8 之后才会支持。

  • @Inherited :规定注解能否从父类中继承
  • @Repeatable :规定注解是否可以重复,重复型的注解还需要指明注解容器,用来存储可重复性注解,同样也是 Java 8 之后才支持

总结

本篇文章用实例讲解了 Java 中注解(Annotation)的概念,以及一些基本的组成部分,包括注解使用的保留策略、作用的目标元素等;之后重点介绍了元注解的概念以及 Java 标准库中预定义的 5 种元注解,这 5 种元注解代表的含义及其用法,其中要注意有些特性是 Java 8 之后才提供支持的,比如 @Repeatable 注解,使用过程中要注意这一点。

(未完待续, 关注作者追踪下篇)

推荐阅读更多精彩内容