- Spring快速入门
- 崔彦威 卢欣欣 王倩
- 2577字
- 2020-08-28 21:51:52
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等。
这里只是列举了几个使用场景,其实还有很多地方可以使用注解。