1.1 注解

本节首先介绍注解的基本概念,理解什么是注解、注解的作用是什么。在此基础上通过示例动手操作加深理解。

1.1.1 什么是注解

我们先看官方解释:它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观、更明了的说明,这些说明信息与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。Java注解是附加在代码中的一些元信息,便于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用,包含在java.lang.annotation包中。

看着上面的解释是不是还是一头雾水?其实我们可以更通俗地理解一下。最近几年出现一个词“斜杠青年”,还有黄某某拍摄的广告语:给人贴标签、下定义,总是很容易,而我却不会因为一件事被定性。这里的斜杠青年、贴标签都是把某些属性附加给对象,和注解功能差不多,它提供了一种安全的类似注释的机制,用来将任何信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。我们可以再来理解一下这句话,这里的程序元素可以理解为人,信息或元数据理解为标签,把标签属性(信息或元数据)赋给人(程序元素)。

上面两段基本把什么注解解释出来了,如果还是不知道注解是什么,那也没关系。其实我们在编程中已经用到或者看到过了,比如@Override、@Deprecated。是不是很熟悉?其实它们就是注解。

1.1.2 内置注解

上面的@Override、@Deprecated都是Java中内置的注解,除了这两个还有其他的内置注解。这里列举了几个常用的内置注解以及它们的作用。

● @Deprecated:编译器在编译阶段遇到这个注解时会发出提醒警告,告诉开发者正在调用一个过时的元素,比如过时的方法、过时的类、过时的成员变量。

● @Override:提示子类要复写父类中被@Override修饰的方法。

● @SuppressWarnings:阻止警告的意思。调用被@Deprecated注解的方法后,编译器会警告提醒,而有时候开发者会忽略这种警告,他们可以在调用的地方通过@SuppressWarnings:达到目的。

● @SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生unchecked这样的警告。它是在Java 1.7的版本中加入的。

● @FunctionalInterface:函数式接口注解,这个是Java 1.8版本引入的新特性。函数式编程很火,所以Java 8也及时添加了这个特性。函数式接口(Functional Interface)就是一个具有一个方法的普通接口。

1.1.3 元注解

通过前面的两小节,我们应该对注解有了一定的认识,下面进一步地了解一下注解。我们在自定义注解时会出现图1-1所示的一些选项。

图1-1

这些选项@Retention、@Target、@Documented其实就是元注解。在创建时配置这些元注解,我们也可以推断出元注解的作用是什么。元注解负责注解自定义注解。java.lang.annotation提供了5种元注解,专门注解其他的注解:

● @Retention:什么时候使用该注解。

● @Target:注解用于什么地方。

● @Documented:注解是否将包含在JavaDoc中。

● @Inherited:是否允许子类继承该注解。

● @Repeatable:指定注解可重复使用。

1.@Retention定义注解的生命周期

● RetentionPolicy.SOURCE:在编译阶段丢弃。这些注解在编译结束之后不再有任何意义,所以它们不会写入字节码。@Override和@SuppressWarnings都属于这类注解。

● RetentionPolicy.CLASS:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。

● RetentionPolicy.RUNTIME:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.@Target表示注解用于什么地方

默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:

● ElementType.CONSTRUCTOR:用于描述构造器。

● ElementType.FIELD:成员变量、对象、属性(包括enum实例)。

● ElementType.LOCAL_VARIABLE:用于描述局部变量。

● ElementType.METHOD:用于描述方法。

● ElementType.PACKAGE:用于描述包。

● ElementType.PARAMETER:用于描述参数。

● ElementType.TYPE:用于描述类、接口(包括注解类型)或enum声明。

3.@Documented是一个简单的Annotations标记注解

表示是否将注解信息添加在Java文档中。

4.@Inherited定义注解和子类的关系

@Inherited元注解是一个标记注解,阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,那么这个annotation将被用于该class的子类。

5.@Repeatable指定注解可重复使用

使用@Repeatable修饰表示该注解可以为重复使用。

1.1.4 自定义注解

元注解是负责注解自定义注解的。自定义注解时是有一些规则限制的,具体如下:

● Annotation型定义为@interface,所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。

● 参数成员只能用public或默认(default)这两个访问权修饰。

● 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组。

● 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象,因为除此之外没有其他获取注解对象的方法。

● 注解也可以没有定义成员。

我们这里自定义一个注解来练习一下,主要用来演示自定义注解以及注解的继承。

1.定义CustomDescription注解

CustomDescription注解相当于标签。为了能多贴标签,又定义了注解容器CustomDescriptions。其中,@Retention(RUNTIME)表示在运行时环境也可以获取注解,@Inherited表示可继承,@Repeatable(CustomDescriptions.class)表示该注解可多次使用。

 package CusAnnontation;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Documented;
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 import java.lang.annotation.Repeatable;
     @Documented
     @Retention(RUNTIME)
     @Target(TYPE)
     @Inherited
     @Repeatable(CustomDescriptions.class)
 public @interface CustomDescription {
     String description() default "";
     }

CustomDescriptions容器:

 package CusAnnontation;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import java.lang.annotation.Documented;
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
     @Documented
     @Retention(RUNTIME)
     @Target(TYPE)
     @Inherited
 public @interface CustomDescriptions {
     CustomDescription[] value();
     }

2.实现继承关系

这里为了演示,我们创建了两个类:一个基类Person,一个子类Student。在Person类加两个自定义注解,在Student中加一个自定义注解。

Person

Student

3.通过反射获取注解属性值

这里我们想通过反射(可以先不要理解)获取Student类的注解值,那么问题来了,它是输出什么的呢?会输出“description:学生”吗?并不是,而是输出父类Person的注解。

输出:

     description:基类
     description:人

如果想输出子类Student的注解该怎么设置呢?很简单,只需在子类Student上覆盖父类的注解就好。

     @CustomDescription(description="学生")
     @CustomDescription(description="人")
 public class Student extends Person

输出:

     description:学生
     description:人

此时输出的就是子类的注解值了。这里我们还可以验证@Retention生命周期的作用,只需要把@Retention(RUNTIME)改成CLASS,再运行就会报错,因为main方法中的custormDescriptions对象是一个null空值。不过自定义注解一般来说都是使用@Retention(RUNTIME)。

1.1.5 注解使用场景介绍

在上一小节通过实例学习了自定义注解的使用,之后就该解决怎么用的问题了。其实,注解应用的场景还是挺多的。

(1)使用注解做bean的属性值校验,例如在开发Java服务器端代码时,会要求对外部传来的参数合法性进行验证。hibernate-validator提供了一些常用的参数校验注解。

(2)使用注解做权限控制。例如,shiro框架中有5个权限注解,我们也可以自定义注解进行权限控制。

(3)代替配置文件功能,像Spring基于注解的配置,减少了xml的配置。

(4)可以生成文档,像Java代码注释中的@see、@param等。

这里只是列举了几个使用场景,其实还有很多地方可以使用注解。