引言
曾几何时,XML
一直是 Java
各大框架配置元数据(meta data
) 的主要途径。但作为一种集中式的元数据管理工具,配置项与作用代码距离太过 “遥远”,非常不利于代码的维护和调试。再加上 XML
本身复杂的语法结构,往往令码农们大感头疼。一种与作用代码耦合在一起的元数据配置方式呼之欲出。于是 注解 (Annotations)就在 JDK 5
中正式出现在开发者的视线之中了。
日常使用 Spring Boot
的同学对于注解的使用肯定再熟悉不过了。但大家有没有想过这些注解,例如 @Configuration @Component @Bean
等等之类的,其本质到底是什么,又是怎样发挥自己特定的作用呢?
注解的本质
oracle 官网对注解的定义为:
Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.
注解是元数据的一种形式,它提供有关程序的数据,该数据不属于程序本身。 注解对其注释的代码操作没有直接影响。
从这个定义里我们可以看出,首先注解携带的是元数据,其次,它可能会引起一些和元数据相关的操作,但不会对被注释的代码逻辑产生影响。
而在JDK
的Annotation接口中有一行注释如此写到:
/**
* The common interface extended by all annotation types.
* ...
*/
public interface Annotation {...}
复制代码
这说明其他注解都扩展自 Annotation
这个接口,也就是说注解的本质就是一个接口。
以 Spring Boot 中的一个注解为例:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
复制代码
它实际上相当于:
public interface Component extends Annotation{...}
复制代码
而@interface
可以看成是一个语法糖。
注解的要素
我们依然来看 @Component
这个例子:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
复制代码
在注解定义上有几个注解@Target, @Retention, @Documented
,被称为 元注解。
所谓元注解就是说明注解的注解,这就好比 Python
中的元类。
Java
中的元注解共有以下几个:
@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();
}
复制代码
可以看到,这个注解的 value 值是一个数组,这也就意味着注解的作用对象可以有多个。 其取值范围都在ElementType
这个枚举之中:
public enum ElementType {
/** 类、接口、枚举定义 */
TYPE,
/** 字段,包括枚举值 */
FIELD,
/** 方法 */
METHOD,
/** 参数 */
PARAMETER,
/** 构造方法 */
CONSTRUCTOR,
/** 局部变量 */
LOCAL_VARIABLE,
/** 元注解 */
ANNOTATION_TYPE,
/** 包定义 */
PACKAGE...
}
复制代码
不同的值代表被注解可修饰的范围,例如TYPE
只能修饰类、接口和枚举定义。这其中有个很特殊的值叫做 ANNOTATION_TYPE
, 是专门表示元注解的。
在回过头来看 @Component
这个例子, Target
取值为 TYPE
。熟悉 Spring Boot
的同学也一定知道,@Component
确实是不能放到方法或者属性前面的。
@Retention
@Retention
注解指定了被修饰的注解的生命周期。定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
复制代码
可以看到这个注解带一个 RetentionPolicy
的枚举值:
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
复制代码
SOURCE
表示注解编译时可见,编译完后就被丢弃。这种注解一般用于在编译器做一些事情;CLASS
表示在编译完后写入 class 文件,但在类加载后被丢弃。这种注解一般用于在类加载阶段做一些事情;RUNTIME
则表示注解会一直起作用。
@Documented
这个注解比较简单,表示是否添加到 java doc
中。
@Inherited
这个也比较简单,表示注解是否被继承。这个注解不是很常用。
注意:元注解只在定义注解时被使用!
注解的架构
从上面的元注解可以了解到,一个注解可以关联多个 ElementType
,但只能有一个 RetentionPolicy
:
Java 内置注解
Java 中有三个常用的内置注解,其实相信大家都用过或者见过。不过在了解了注解的真实面貌以后,不妨重新认识一下吧!
@Override
它的定义为:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
复制代码
可见这个注解没有任何取值,只能修饰方法,而且RetentionPolicy 为 SOURCE,说明这是一个仅在编译阶段起作用的注解。
它的真实作用想必大家一定知道,就是在编译阶段,如果一个类的方法被 @Override
修饰,编译器会在其父类中查找是否有同签名函数,如果没有则编译报错。可见这确实是一个除了在编译阶段就没什么用的注解。
@Deprecated
它的定义为:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
复制代码
这个注解也没有任何取值,能修饰所有的类型,永久存在。这个注解的作用是,告诉使用者被修饰的代码不推荐使用了,可能会在下一个软件版本中移除。这个注解仅仅起到一个通知机制,如果代码调用了被@Deprecated 修饰的代码,编译器在编译时输出一个编译告警。
@SuppressWarnings
它的定义为:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
复制代码
这个注解有一个字符串数组的值,需要我们使用注解的时候传递。可以在类型、属性、方法、参数、构造函数和局部变量前使用,声明周期是编译期。
这个注解的主要作用是压制编译告警的。
例如
public static void main(String[] args) {
Date date = new Date(2020, 5, 22);
}
复制代码
我们可以看到,Date
的这个构造函数是被@Deprecated 修饰的:
@Deprecated
public Date(int year, int month, int date) {
this(year, month, date, 0, 0, 0);
}
复制代码
所以上面的代码在编译时会报一个Warning
:
java: java.util.Date 中的 Date(int,int,int) 已过时
复制代码
为了不让编译器输出这个 Warning
, 就需要在上述的 main
方法前面增加一个 @SuppressWarnings
注解:
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2020, 5, 22);
}
复制代码
注解输入的字符串deprecated
表明让编译器忽略 @Deprecated
注解引发的编译告警。
总结和预告
本篇主要介绍了 Java
注解的本质、要素以及元注解、三种 Java
内置注解。
下篇我们将站在JVM
的角度深入分析注解的功能实现原理,并手写一个自己的注解。