Java 语言提供了一个很好的特性 Annotation, 中文常翻译为注解,在平时的编程中也比较常用,最明显的好处就是让代码变得更简洁明了。比如,标记一个函数是否重写了父类方法或者实现了接口的抽象方法,用 @Override
在方法上注解;声明一段函数显示地执行开启事务操作用 @Transaction
注解。
但是注解本身并不是程序的一部分,它并不会对代码的执行有直接的影响,比如把 @Override
注解去掉,重写还有效的,重写本身并不是受 Override
控制的。而像 @Transaction
这种注解去掉后,AutoCommit
的设置就会是 true
,每条 SQL 就会作为一个事务自动提交。似乎逻辑受影响了?其实不然,而是我们利用了 Spring 的 AOP 的机制,扫描到有 @Transaction
注解的代码,加了一些开启事务、提交事务的逻辑,这个逻辑是在单独的一块代码中实现的,我们只是通过注解来识别了需要加这个逻辑的代码片段。
所以,注解本质上是一种程序的标记行为。
基本概念
Java注解用 @ 符号这种语法形式标记,它由两个基本部分组成:
- 名字 ( name )
- 元素 ( 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
关键字来设置元素的默认值。
除了名字、元素声明之外,声明一个注解的时候还需要在这个注解之上再加上注解,比如上面例子中的 Target
、Retention
、Documented
. 这引出了下面一个重要的概念:元注解。
元注解
简单地说,元注解( 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 注解,使用过程中要注意这一点。
(未完待续, 关注作者追踪下篇)