引言
曾几何时,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@Indexedpublic @interface Component { String value() default "";}
它实际上相当于:
public interface Component extends Annotation{...}
而@interface 可以看成是一个语法糖。
注解的要素
我们依然来看 @Component 这个例子:
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Indexedpublic @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 not 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. * *
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 修饰的:
@Deprecatedpublic 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 内置注解。
分享资料,私信回复【电子书】即可免费领取~
最后这里我为大家准备了最新面试资料与电子书免费领取,关注回复【电子书】即可获得~
海量电子书,珍藏版