JAVA基础之注解
1、注解的作用
Annotation(注解)是JDK 5.0引入的特性,它的基本作用就是修饰编程元素。
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。编译器、开发工具或其他程序可以用反射来了解该类,有什么样的标记,就去干相应的事。
注解的语法比较简单,除了@符号的使用之外,与Java固有语法一致。JDK 5.0内置了三种标准注解:
-
@Override
:表示当前的方法定义将覆盖父类中的方法。 -
@Deprecated
:表示当前元素不推荐使用,是被弃用的代码。 -
@SuppressWarnings
:忽略编译器发出的警告信息。
注解有以下几个作用:
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有
@see
、@param
、@return
等 - 跟踪代码依赖性,替代配置文件的功能。比如Spring从 2.5版本 开始支持基于注解的配置。现在的框架基本都使用了注解来减少配置文件的数量。
- 在编译时进行格式检查。如
@override
放在方法前,如果这个方法并不是覆盖了父类方法,则编译时就能检查出来。
2、元注解
Java 5.0提供了四种元注解——负责注解其他注解。
2.1 @Target
@Target
说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围,即该注解可以用在什么地方。
取值(ElementType枚举类)有:
- CONSTRUCTOR:用于描述构造器
- FIELD:用于描述变量
- LOCAL_VARIABLE:用于描述局部变量
- METHOD:用于描述方法
- PACKAGE:用于描述包
- PARAMETER:用于描述参数
- TYPE:用于描述类、接口(包括注解类型) 或enum声明
2.2 @Retention
@Retention
定义了该注解被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用@Retention
可以限制注解的“生命周期”。
作用:用于描述注解的生命周期,即注解在什么范围内有效。
取值(RetentionPoicy枚举类)有:
- SOURCE:在源文件中有效
- CLASS:在class文件中有效
- RUNTIME:在运行时有效
2.3 @Documented
@Documented
是一种标记性注解,被其标记的注解会被包含在javadoc文档中。
2.4 @Inherited
@Inherited
也是一种标记性注解,被其标记的注解会允许子类继承。如果一个使用了@Inherited
修饰的annotation类型被用于一个class,则该注解将被用于该class的子类。
需要注意的是,被@Inheriten
标记的注解只会被class的子类所继承,类并不从它所实现的接口继承annotation,方法也不从它所重载的方法继承annotation。
如果我们使用java.lang.reflect
去查询一个@Inherited
类型的annotation时,反射代码将检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
3、自定义注解
自定义注解的格式为:
public @interface 注解名 {定义体}
例如:
@Target(value = { ElementType.FIELD })
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
int code() default -1;
String message() default "";
}
@interface
用来声明一个注解,自动继承了java.lang.annotation.Annotation
接口,在自定义注解时,不能继承其他的注解或接口。
定义体中的每一个方法实际上是声明了一个配置参数,方法只能用public或默认(default)两种访问权修饰,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
注解参数支持数据类型:
所有基本数据类型(byte,int,short.long,double,float,boolean,char)
- String类型
- Class类型
- Enum类型
- Annotation类型
- 以上所有类型的数组
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难判断一个元素是否缺失,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。
如果成员名称是value
,在赋值过程中可以简写;如果成员类型为数组,但是只赋值一个元素,则也可以简写。上面的例子可以简写为:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotNull {
int code() default -1;
String message() default "";
}
上例自定义了名为@NotNull
的注解,作用对象为field
,其目的是对类变量进行非空检验。可是,到底怎么怎么利用它来进行检验呢?
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。从原理上讲,注解处理器就是通过反射机制获取被检查字段上的注解信息,然后根据注解元素的值进行特定的处理。
比如,在某网站注册用户,用户输入的信息会被封装成一个User
对象:
public class User {
@NotNull(message = "用户昵称不能为空")
private String userName;
@NotNull(message = "密码不能为空")
private String password;
private Integer age;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
要求用户昵称与密码不能为空,因此userName
与password
字段被@NotNull
注解。
后台程序接收到User
对象后,可以通过反射机制探知哪些字段被@NotNull
注解了,如果全部满足要求再进行下一步业务逻辑;反之,返回错误页面。
public class UserTest {
public static boolean testNotNull(User user) throws IllegalArgumentException, IllegalAccessException {
Class<?> clazz = user.getClass(); // 获取User的Class对象
Field[] userFields = clazz.getDeclaredFields(); // 获取User的所有Field对象
for (Field field : userFields) {
field.setAccessible(true); // User中的Field都是private的,所以要先setAccessible
NotNull notNullAnnotation = field.getAnnotation(NotNull.class); // 获取Field的NotNull注解对象
if (notNullAnnotation != null) { // 如果该Field被NotNull注解了,那么字段不能为空
if (field.get(user) == null) {
System.out.println(notNullAnnotation.message());
return false;
}
}
}
return true;
}
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
User user = new User();
user.setUserName("chenlongfei");
testNotNull(user);
}
}
由于只填写了昵称而没填密码,不能通过验证,控制台打印出:
密码不能为空
而该信息正是在@NotNull
注解password
字段时由message
定义的。