End

注解 Annotation 简介 总结

本文地址


目录

注解 Annotation 简介 总结

在java中,注解作为程序的元数据嵌入到程序当中,元数据标签的存在并不影响程序代码的编译和执行

所谓Annotation就是提供了一种为程序元素设置元数据的方法,可用于修饰包、类、构造器、方法、成员变量、参数和局部变量的声明。注解可以被一些解析工具或者是编译工具进行解析。Annotation中的信息可以在编译、加载和运行时被读取,并执行相应的处理。

当前许多Java框架中大量使用注解,如Hibernate、Jersey、Spring。

背景知识

什么是元数据 Metadata

元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

元数据是指从信息资源中抽取出来的用于说明其特征、内容的结构化的数据(如题名、版本、出版数据、相关说明、检索点等),用于组织、描述、检索、保存、管理信息和知识资源。

任何文件系统中的数据都分为数据和元数据。数据是指普通文件中的实际数据,而元数据指用来描述一个文件的特征的系统数据,诸如访问权限、文件拥有者以及文件数据块的分布信息(inode…)等等。在集群文件系统中,分布信息包括文件在磁盘上的位置以及磁盘在集群中的位置。用户需要操作一个文件必须首先得到它的元数据,才能定位到文件的位置并且得到文件的内容或相关属性。

HTML的head里有一个meta标签。根据上面的解释,我们应该知道它是“关于文档的信息”了。meta的属性有两种,name和http-equiv,name属性用来描述网页的内容,http-equiv属性指示服务器在发送实际的文档之前先在要传送给浏览器的 MIME 文档头部包含名称/值对,比如用以说明主页制作所使用的文字以及语言。

以下为百度和新浪首页的meta标签:

//百度首页meta标签
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta name="referrer" content="always">
<meta name="theme-color" content="#2932e1">
//新浪首页部分meta标签
<meta name="keywords" content="新浪,新浪网,SINA,sina,sina.com.cn,新浪首页,门户,资讯" />
<meta name="description" content="新浪网为全球用户24小时提供全面及时的中文资讯,***。" />
<meta name="application-name" content="新浪首页"/>

基于元数据的广泛应用,JDK5.0引入了Annotation的概念来描述元数据。

为什么要引入 Annotation

使用Annotation之前,XML被广泛的应用于描述元数据。不知何时开始一些应用开发人员和架构师发现XML的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像XML那样和代码是松耦合的(在某些情况下甚至是完全分离的)代码描述。如果你在Google中搜索“XML vs. annotations”,会看到许多关于这个问题的辩论。最有趣的是XML配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你很疑惑,两者观点似乎构成了一种循环,但各有利弊。下面我们通过一个例子来理解这两者的区别。

假如你想为应用设置很多的常量或参数,这种情况下,XML是一个很好的选择,因为它不会同特定的代码相连。如果你想把某个方法声明为服务,那么使用Annotation会更好一些,因为这种情况下需要注解和方法紧密耦合起来,开发人员也必须认识到这点。

另一个很重要的因素是Annotation定义了一种标准的描述元数据的方式。在这之前,开发人员通常使用他们自己的方式定义元数据。例如,使用标记interfaces,注解,transient关键字等等。每个程序员按照自己的方式定义元数据,而不像Annotation这种标准的方式。

目前,许多框架将XML和Annotation两种方式结合使用,平衡两者之间的利弊。

XML 和 Annotation 的优缺点

目前 web 应用中几乎都使用 xml 作为配置项,xml 之所以这么流行,是因为它的很多优点是其它技术的配置所无法替代的。
xml的优点:

  • xml 作为可扩展标记语言最大的优势在于开发者能够为软件量身【定制】适用的标记,使代码更加通俗易懂。
  • 利用 xml 配置能使软件更具扩展性。例如 Spring 将 class 间的依赖配置在 xml 中,最大限度地提升应用的可扩展性。
  • 具有成熟的验证机制确保程序正确性。利用 Schema 或 DTD 可以对 xml 的正确性进行验证,避免了非法的配置导致应用程序出错。
  • 修改配置而无需变动现有程序。

虽然有如此多的好处,但毕竟没有什么万能的东西,xml 也有自身的缺点。xml的缺点:

  • 需要解析工具或类库的支持。
  • 解析 xml 势必会影响应用程序性能,占用系统资源。
  • 配置文件过多导致管理变得困难。
  • 编译期无法对其配置项的正确性进行验证,或要查错只能在运行期。
  • IDE 无法验证配置项的正确性。
  • 查错变得困难,往往配置的一个手误导致莫名其妙的错误。
  • 开发人员不得不同时维护代码和配置文件,开发效率变得低下。
  • 配置项与代码间存在潜规则。改变了任何一方都有可能影响另外一方。

Annotation 的优点:

  • 保存在 class 文件中,降低维护成本。
  • 无需工具支持,无需解析。
  • 编译期即可验证正确性,查错变得容易。
  • 提升开发效率。

Annotation的缺点:

  • 若要对配置项进行修改,不得不修改 Java 文件,重新编译打包应用。
  • 配置项编码在 Java 文件中,可扩展性差。

没有一个事物是万能的,同样 xml 和 java Annotation 都有各自的优缺点。且他们的优缺点恰恰是互补的,xml 的强项是 Annotation 所不具备的,而 Annotation 的优势也是 xml 所欠缺的。这也正是时下流行的 xml + Annotation 配置的原因所在。

注解的定义

定义一个Annotation类型使用@interface关键字,定义一个Annotation类型与定义一个接口非常像(只是多了一个@符号)。

public @interface TestAnnotation {
}

Annotation可以是上面的简单形式,还可以包含成员变量。成员变量的一些规则:

  • Annotation的成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型
  • Annotation的属性类型只能是基本类型、String、enum、Class及上述类型的一维数组类型
  • 使用带有属性的Annotation时,必须为其所有定义的属性指定值(使用default的可以不用指定)
  • 定义Annotation时可以使用default关键字为属性设置默认值,使用时不为该属性指定值时会使用默认值
  • 如果Annotation中具有名为value的属性,在使用时如果只使用value属性的话,可以不写属性名直接指定值
  • 对于数组类型,当数组中只有一个元素时,可以省略大括号

注解的定义案例

UserInfo

public @interface UserInfo {
    String username();
    String data() default "2017年9月2日";
    int age();
    SexEnum sex();
    
    public static enum SexEnum {
        男, 女, 其他
    }
}

Citys

public @interface Citys {
    String username() default "包青天";
    String[] value();
}

使用

@UserInfo(age = 28, sex = UserInfo.SexEnum.男, username = "白乾涛")
public class Test {
    
    @Citys({ "广州", "深圳", "长沙", "呵呵", })  //后面可以带一个逗号,以方便扩展
    @UserInfo(age = 28, sex = UserInfo.SexEnum.男, username = "白乾涛", data = "2017.9.2")
    public static void main(String[] args) {
        @Citys(value = { "广州", "深圳", "长沙", "呵呵" }, username = "白乾涛")
        int i = 1;
    }

    @Citys("广州")
    String string = "只有一个元素时,可以省略大括号";
}

四个元注解

元注解的作用就是负责注解其他注解

Java5.0定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明(只能作用在注解上,不能作用在其他程序元素上)。

Java5.0定义的四个元注解为:@Target@Retention@Documented@Inherited
这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。

Target 适用的元素种类

Target 的完整定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface java.lang.annotation.Target {
    /**
     * @return an array of the kinds of elements an annotation type can be applied to
     */
    ElementType[] value();
}

指示注解类型所适用的程序元素的种类

@Target用来限定一个Annotation可以用于修饰哪些程序元素(可修饰的程序元素由 ElementType 限定),例如方法、成员变量等。

如果注解类型声明中不存在 Target 元注解,则声明的类型可以用在任一程序元素上;如果存在这样的元注解,则编译器强制实施指定的使用限制。

如上例中的 UserInfo,其可以作用到类(类是TYPE的一种)和方法(METHOD)上,所以用@Target来限定的话就是:

@Target(value = { ElementType.TYPE, ElementType.METHOD })
public @interface UserInfo { ... }

同样,用元注解@Target对注解Citys来限定的话就是:

@Target(value = { ElementType.METHOD, ElementType.LOCAL_VARIABLE, ElementType.FIELD })
public @interface Citys { ... }

如果一个注解使用元注解@Target(ElementType.ANNOTATION_TYPE)来限定,则表明此注解只能用在注解类型元素的声明上,那么此注解就是一个元注解。实际上,四个元注解就是这么定义的:

@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {...}

Retention 保留策略

Retention 的完整定义

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface java.lang.annotation.Retention {
    /**
     * @return the retention policy
     */
    RetentionPolicy value();
}

指示注解类型的注解要保留多久。

如果注解类型声明中不存在 Retention 注解,则保留策略默认为 RetentionPolicy.CLASS

注意,使用默认保留策略的注解,不能通过反射获取注解信息。

只有元注解类型直接用于注解时,Target 元注解才有效。如果元注解类型用作另一种注解类型的成员,则无效。

四个元注解的定义都是@Retention(RetentionPolicy.RUNTIME),也即编译器将把注解记录在类文件中,在运行时 VM 将保留注解,因此可以通过反射读取。

Documented 文档化

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

Indicates指示、表明、标志 that annotations with a type are to be documented by javadoc and similar tools by default. This type should be used to annotate注释、注解、批注、注 the declarations声明、宣言、发表 of types whose annotations affect the use of annotated elements by their clients. If a type declaration is annotated with Documented, its annotations become part of the public API of the annotated elements.

指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。应使用此类型来注解这些类型的声明:其注解会影响由其客户端注解的元素的使用。如果一个类型的声明是用 Documented 来注解的,则其注解将成为注解元素的公共 API 的一部分。

效果测试

//@Documented
public @interface TestDocumented {
    String value();
}

@TestDocumented("白乾涛")
public class Test {
    @TestDocumented("白乾涛")
    public static void main(String[] args) {
    }
}

如果不加@Documented,默认情况下,javadoc是不包括注解的,此时生成的文档如下:

如果声明注解时指定了 @Documented,则它就会被 javadoc 之类的工具处理,所以注解类型信息也会被包括在生成的文档中,此时生成的文档如下:

其实没有什么卵用

Inherited 自动继承

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface java.lang.annotation.Inherited {
}

Indicates指示、表明、标志 that an annotation type is automatically inherited. If an Inherited meta-annotation is present存在 on an annotation type declaration, and the user queries查询 the annotation type on a class declaration, and the class declaration has no annotation for this type, then the class's superclass will automatically be queried for the annotation type. This process will be repeated until an annotation for this type is found, or the top of the class hierarchy (Object) is reached. If no superclass has an annotation for this type, then the query will indicate that the class in question has no such annotation.

指示注解类型被自动继承。如果在注解类型声明中存在 Inherited 元注解,并且用户在某一类声明中查询该注解类型,同时该类声明中没有此类型的注解,则将在该类的超类中自动查询该注解类型。此过程会重复进行,直到找到此类型的注解或到达了该类层次结构的顶层 (Object) 为止。如果没有超类具有该类型的注解,则查询将指示当前类没有这样的注解。

Note that this meta-annotation type has no effect if the annotated type is used to annotate anything other than a class. Note also that this meta-annotation only causes annotations to be inherited from superclasses; annotations on implemented interfaces have no effect.

注意,如果使用注解类型注解类以外的任何事物,此元注解类型都是无效的。还要注意,此元注解仅促成从超类继承注解;对已实现接口的注解无效。

这是一个稍微复杂的注解类型,它指明被注解的类会自动继承。更具体地说,如果定义注解时使用了 @Inherited 标记,然后用定义的注解来标注另一个父类,父类又有一个子类,则父类的所有属性将被继承到它的子类中。

基本没用到过

三个基本的注解

在 java.lang 包中提供了3个基本Annotation的用法,可以通过查看API文档来了解。

Override 重写

@Target(ElementType.METHOD)//可以看出,@Override只能修饰方法,不能修饰其他程序元素
@Retention(RetentionPolicy.SOURCE)
public @interface java.lang.Override {
}

表示一个方法声明打算重写超类中的另一个方法声明。如果方法利用此注释类型进行注解,但没有重写超类方法,则编译器会生成一条错误消息。

Deprecated 废弃

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface java.lang.Deprecated {
}

A program element annotated @Deprecated is one that programmers are discouraged from using, typically because it is dangerous, or because a better alternative exists. Compilers warn when a deprecated program element is used or overridden in non-deprecated code.

使用@Deprecated注释的程序元素,是不鼓励程序员使用的元素,通常是因为它很危险或存在更好的选择。 在使用不被赞成的程序元素,或在不被赞成的代码中执行重写时,编译器会发出警告。

SuppressWarnings 不警告

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface java.lang.SuppressWarnings {
    /**
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

Indicates that the named compiler warnings指定的编译器警告 should be suppressed压制、取消 in the annotated element (and in all program elements contained in the annotated element).

指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中,取消显示指定的编译器警告。

Note that the set of warnings suppressed in a given element is a superset of the warnings suppressed in all containing elements. For example, if you annotate a class to suppress one warning and annotate a method to suppress another, both warnings will be suppressed in the method.

注意,在给定元素中取消显示的警告集,是所有包含元素中取消显示的警告的超集。例如,如果注释一个类来取消显示某个警告,同时注释一个方法来取消显示另一个警告,那么将在此方法中同时取消显示这两个警告。

As a matter of style, programmers should always use this annotation on the most deeply nested element where it is effective有效. If you want to suppress a warning in a particular特定的 method, you should annotate that method rather than its class.

根据风格不同,程序员应该始终在最里层的嵌套元素上使用此注释,在那里使用才有效。例如,如果要在特定的方法中取消显示某个警告,则应该注释该方法而不是注释它的类。

两个相关的枚举类

ElementType 元素类型

public enum java.lang.annotation.ElementType extends Enum<ElementType>

程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。
这些常量与 Target 元注解类型一起使用,以指定在什么情况下使用注解类型是合法的。

枚举常量

  • ANNOTATION_TYPE 注解类型声明
  • CONSTRUCTOR 构造方法声明
  • FIELD 字段声明(包括枚举常量)
  • LOCAL_VARIABLE 局部变量声明
  • METHOD 方法声明
  • PACKAGE 包声明
  • PARAMETER 参数声明
  • TYPE 类、接口(包括注解类型)或枚举声明
  • TYPE_PARAMETER @since 1.8
  • TYPE_USE @since 1.8

方法

static ElementType    valueOf(String name)

返回带有指定名称的该类型的枚举常量。
字符串必须与用于声明该类型的枚举常量的 标识符 完全匹配(不允许有多余的空格,大小写敏感)。
如果该枚举类型没有带有指定名称的常量, - 则抛出 IllegalArgumentException

static ElementType[]    values()

Returns an array containing the constants of this enum type, in the order they are declared.

案例:

System.out.println(ElementType.values().length);//10
for (ElementType c : ElementType.values()) {
    System.out.print(c + "  ");//TYPE  FIELD  METHOD  PARAMETER  CONSTRUCTOR
    //LOCAL_VARIABLE  ANNOTATION_TYPE  PACKAGE  TYPE_PARAMETER  TYPE_USE  
}
System.out.println(ElementType.valueOf("FIELD"));//FIELD

RetentionPolicy 保留策略

public enum java.lang.annotation.RetentionPolicy extends Enum<RetentionPolicy>

注解保留策略。此枚举类型的常量描述保留注解的不同策略。它们与 Retention 元注解类型一起使用,以指定保留多长的注解。

枚举常量

  • CLASS 在class文件中有效。编译器将把注解记录在类文件中,但在运行时 VM 不需要保留注解。这是默认的行为。也就是说,默认行为是:当运行Java程序时,JVM不可获取Annotation信息。
  • RUNTIME 在运行时有效。编译器将把注解记录在类文件中,在运行时 VM 将保留注解,因此可以反射性地读取。
  • SOURCE 只在源文件中有效。编译器要丢弃的注解。

方法

static RetentionPolicy    valueOf(String name)

返回带有指定名称的该类型的枚举常量。
字符串必须与用于声明该类型的枚举常量的 标识符 完全匹配(不允许有多余的空格,大小写敏感)。
如果该枚举类型没有带有指定名称的常量, - 则抛出 IllegalArgumentException

static RetentionPolicy[]    values()

Returns an array containing the constants of this enum type, in the order they are declared.

案例:

System.out.println(RetentionPolicy.values().length);//3
for (RetentionPolicy r : RetentionPolicy.values()) {
    System.out.print(r + "  ");//SOURCE  CLASS  RUNTIME  
}
System.out.println(RetentionPolicy.valueOf("CLASS"));//CLASS

一个接口 Annotation

public interface java.lang.annotation.Annotation 

The common interface extended by all annotation types. Note that an interface that manually extends this one does not define an annotation type. Also note that this interface does not itself define an annotation type.

所有 annotation 类型都要扩展的公共接口。注意,手动扩展该公共接口的接口不定义 annotation 类型。还要注意此接口本身不定义 annotation 类型。

More information about annotation types can be found in section 9.6 of The Java™ Language Specification. The java.lang.reflect.AnnotatedElement interface discusses compatibility兼容性 concerns when evolving an annotation type from being non-repeatable to being repeatable.

有关注解类型的更多信息,请参见Java™语言规范第9.6节。 java.lang.reflect.AnnotatedElement接口讨论了,将注解类型从不可重复转变为可重复时的,兼容性问题。

方法

  • boolean equals(Object obj)
  • int hashCode()
  • String toString() 返回此 annotation 的字符串表示形式。表示形式的细节取决于实现,但下面的情况是最常见的@com.bqt.Citys(username=包青天, value=[广州, 深圳])
  • Class<? extends Annotation> annotationType() 返回此 annotation 的注解类型。

案例:

@Citys({ "广州", "深圳", })//必须设置保留策略为 RUNTIME 才能通过反射获取注解信息
Citys ann = method.getAnnotation(Citys.class);
System.out.println(ann.username() + "  " + Arrays.toString(ann.value()) + "  " + ann.annotationType());//包青天  [广州, 深圳]  interface com.bqt.Citys
System.out.println(ann.toString());//@com.bqt.Citys(username=包青天, value=[广州, 深圳])

2017-9-3

posted @ 2017-09-03 15:49  白乾涛  阅读(9899)  评论(0编辑  收藏  举报