Java高级系列——注解(Annotations)
一、介绍
本系列文章的这一部分我们将会介绍Java 5版本引入的除泛型和枚举之外的另外一个强大特性:注解,可以将注解看成一种特殊的接口。
注解是一种特殊种类的元数据,它能够关联Java语言中不同元素和结构。有意思的是,在Java生态系统中大多数使用样板XML描述符的地方,注解在消除这些XML描述符上做出了很大的贡献。注解引入了新的,类型安全以及非常强健的配置和个性化技术。
二、注解作为特殊接口
就像我们在前文中所提到的一样,注解用来关联Java语言中的元数据和不同的元素。
注解本身对它所注解的元素不会造成任何直接的影响。但是,依靠注解和它的定义方式,它们可以被Java编译器(注解最好的实例就是我们前面的文章中所使用的@Override注解)、注解处理器和运行时代码使用反射和其他的虚拟机内省技术使用。
让我们来看一个最简单的注解声明:
public @interface SimpleAnnotation { }
@interface关键字引入了新的注解类型,这也是为何注解可以被当做专门的接口看待,注解可以声明有默认值和没有默认值的属性,比如:
public @interface SimpleAnnotationWithAttributes { String name(); int order() default 0; }
如果注解声明了没有默认值的属性,那么在该注解被应用的所用地方都应该提供注解属性值。
@SimpleAnnotationWithAttributes(name = "new annotation")
为了方便,如果注解只有一个属性并且属性的名称是value,那么属性的名称就可以被省略,比如:
public @interface SimpleAnnotationWithValue { String value(); }
上面声明的这个注解就可以按照如下的方式去使用:
@SimpleAnnotationWithValue("new annotation")
注解也有一些限制,在某些情况下使用注解可能会不太方便。
-
首先,注解不支持任何继承:注解不能继承其他的注解。
-
其次,不能通过编码的形式使用new关键字创建注解实例。
-
第三,注解只能声明基本数据类型属性,String或者 Class<?>类型及其数组。
-
第四,注解中不允许声明方法和构造器。
三、注解及其保留策略(Retention Policy)
每个注解都有一个被称为保留策略(Retention Policy)的特征,它是一组如何保留注解的策略组合的枚举(RetentionPolicy类型)。保留策略可以设置为以下的值之一。
策略 | 描述 |
---|---|
CLASS | 注解被编译器记录在class文件中,但是在运行时不需要虚拟机保留(即运行时不存在) |
RUNTIME | 注解被编译器记录在class文件中并且在运行时被虚拟机保留,因此可以通过反射机制获取 |
SOURCE | 注解被编译器丢弃(即注解仅在源码中保留,class文件中不存在) |
保留策略对注解何时可用于处理有至关重要的影响。保留策略可以通过使用@Retention注解来设置。比如:
import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention( RetentionPolicy.RUNTIME ) public @interface AnnotationWithRetention { }
设置注解的保留策略为RUNTIME将会保证注解在编译过程和运行的应用程序中存在。
四、注解以及元素类型(ElementType)
注解的另外一个特征就是每个注解必须有它能够应用的元素类型。有点类似于保留策略,元素类型被定义成一组可能的元素类型的枚举(ElementType)。
元素类型 | 描述 |
---|---|
ANNOTATION_TYPE | 标明注解可用于注解类型声明(应用于另外的注解) |
CONSTRUCTOR | 标明注解可用于构造函数声明 |
FIELD | 标明注解可用于字段/域(包括枚举常量)声明 |
LOCAL_VARIABLE | 标明注解可用于局部变量声明 |
METHOD | 标明注解可用于方法声明 |
PACKAGE | 标明注解可用于包声明 |
PARAMETER | 标明注解可用于参数声明 |
TYPE | 标明注解可用于类、接口(包括注解类型)、枚举类型的声明 |
此外,除了上面所描述的这些元素类型之外,Java 8版本引入了两个新的注解可以使用的元素类型。
元素类型 | 描述 |
---|---|
TYPE_PARAMETER | 标明注解可以写在类型变量的声明语句中 |
TYPE_USE | 表示该注解能写在使用类型的任何语句中(eg:声明语句、泛型和强制转换语句中的类型) |
和保留策略对比,注解可以使用@Target注解声明多个与之相关联的多个元素类型。比如:
import java.lang.annotation.ElementType; import java.lang.annotation.Target; @Target({ElementType.FIELD, ElementType.METHOD}) public @interface AnnotationWithTarget { }
大多数情况下,您要创建的所有注解都应该同时指定保留策略和元素类型才能有用。
五、注解与继承(Annotations and inheritance)
在Java中,声明注解与继承之间存在非常重要的联系。默认情况下,子类不能够继承父类中声明的注解,但是,有一种方法就是可以通过使用@Inherited注解在类层次结构中传递指定的注解。比如:
@Target( { ElementType.TYPE } ) @Retention( RetentionPolicy.RUNTIME ) @Inherited @interface InheritableAnnotation { } @InheritableAnnotation public class Parent { } public class Child extends Parent { }
在这个例子中,被声明在父类中的@InheritableAnnotation注解可以被子类继承。
六、可重复注解(Repeatable annotations)
我们到目前为止还没有讨论过Java 8之前的版本和注解相关的另外的一个限制,即相同的注解在同一个地方只能出现一次,不能重复多次。Java 8通过提供可重复注解的支持放松了这种限制。比如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatableAnnotations { RepeatableAnnotation[] value(); } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Repeatable( RepeatableAnnotations.class ) public @interface RepeatableAnnotation { String value(); }; @RepeatableAnnotation("repeatition 1") @RepeatableAnnotation("repeatition 2") public void performAction() { // Some code here }
虽然在Java 8中可重复注解功能需要做一些工作才能使注解可重复使用(使用@Repeatable),但最终的结果是值得的:更简洁和紧凑的注释代码。
七、注解处理器(Annotation processors)
Java编译器支持一种称为注解处理器的特殊类型的插件(使用-processor命令行参数),它可以在编译阶段处理注解。 注解处理器可以分析注解的作用(执行静态代码分析),创建额外的Java源文件或资源(可以被编译和处理),或者改变注解的代码。
在告知编译器那个注解可以被注解处理器应用和处理,保留策略(Retention Policy)扮演了非常关键的角色。
注释处理器被广泛使用,但是编写一个注解处理器需要一些关于Java编译器如何工作和编译过程本身的知识。
八、注解与配置大于约定(Annotations and configuration over convention)
约定大于配置是一种软件设计惯例,这种惯例旨在简化开发过程,但是开发者需要遵循一些简单的规则(或约定)。比如说,一些MVC框架遵循约定将控制器放在controller目录(或者包)中,一些ORM框架经常遵循约定在model目录查找实体类并从各自的类中获取关系表名称。
另一方面,注解开创了一种不同的设计惯例,即一切基于明确的配置。就我们上面所说的例子而论,@Controller注解可以明确的标记任何类作为控制器,@Entity注解可以关联数据库表。注解的好处也来自于这样的事实,即注解的可拓展性、可拥有额外属性和有限的特定元素类型。Java编译器会强制执行不当的注解使用并在编译阶段很早就发现错误配置问题。
九、何时使用注解
注解实际上无处不在:Java标准库有很多,基本上每个Java规范都包含注解。 每当您需要将附加的元数据与您的代码相关联,注解都是简单直接的方法。
有趣的是,Java社区正在不断努力开发公共通用语义概念并通过多种技术标准(更多信息,请参阅JSR-250规范)化注解。目前,标准Java库中包含以下注解。
注解 | 描述 |
---|---|
@Deprecated | 表明被标记元素已过期并应该不再使用。每当程序使用被该注解注解的方法、类或字段(域)时,编译器会生成警告。 |
@Override | 提示编译器该元素是为了覆盖在父类中声明的元素。 |
@SuppressWarnings | 指示编译器抑制通过其他方式生成的警告。 |
@SafeVarargs | 当应用于方法或构造函数时,声明代码不会对其可变参数执行潜在的不安全操作。使用此注解类型时,与可变参数使用有关的未经检查的警告将被抑制。 |
@Retention | 指定如何保留被标记的注解。 |
@Target | 指定被标记注解可以使用那些Java元素 |
@Documented | 表明使用该注解的元素可以使用Javadoc工具记录(默认情况下,注解是不包含在Javadoc中的) |
@Inherited | 表明注解类型可以从父类继承 |
Java 8版本的发布增加了也增加了几个新的注解。
注解 | 描述 |
---|---|
@FunctionalInterface | 表示类型声明是按照Java语言规范定义的函数接口。 |
@Repeatable | 表明被标记的注解可以在相同的地方应用多次。 |
十、小结
本文我们大概讲述了一些和注解相关内容,通过本文的阅读我们大致可以明白注解可以作为一个特殊的接口,在定义一个新的注解是,我们需要指定注解的保留策略以及元素类型。同时我们也阐述了如何让子类继承父类的注解以及如何声明可重复注解。当然我们也讲述了一些关于注解处理器以及在注解所遵从的配置大于约定的概念。接下来我们将会讨论如何有效的编写方法,敬请期待。