一. 简介
注解是java5的新特性。注解可以看做一种注释或者元数据(MetaData),可以把它插入到我们的java代码中,用来描述我们的java类,从而影响java类的行为。
二. 基本语法
1. 注解的使用形式:
一个java注解由一个@符后面跟一个字符串构成,类似于这样:
@Entity
java注解中一般包含一些元素,这些元素类似于属性或者参数,可以用来设置值,比如我们有一个包含两个元素的@Entity注解:
@Entity(tableName = "vehicles", primaryKey = "id")上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。
上面注解中有两个元素,tableName和primaryKey,它们各自都被赋予了自己的元素值。
2. 注解作用域:
注解可以用于描述一个类、接口、方法、方法参数、字段、局部变量等,例如
//注解一个类
@Entity
public class TestClass {
//注解一个字段
@Persistent
protected String name = null;
//注解一个方法
@Getter
public String getName() {
return this.name;
}
//注解一个参数
public void setName(@Optional name) {
this.name = name;
}
public String testLocal(String name) {
//注解一个局部变量
@Optional
List localNames = names;
...
}
}
3. 声明自定义注解:
创建自定义的注解也比较简单,只需要使用 @interface 关键字即可,如
@interface MyAnnotation {}
这样就创建了一个最简单的注解。
注意,注解是不支持继承的,因此不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口。
4. 元注解:
所谓元注解就是标记其他注解的注解,常用的有以下几种:
1)@Target ,用来约束注解可以应用的地方,即上面提到的作用域,类、方法等。这个元注解接收的值是一个枚举类 ElementType 的数组,这个枚举类有以下值:
public enum ElementType {
/** 标明该注解可以用于类、接口(包括注解类型)或enum声明*/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/** 标明注解可以用于类型参数(泛型)声明,1.8新加入 */
TYPE_PARAMETER,
/** 类型使用声明,可以用于标注任何类型,但不包括Class,1.8新加入 */
TYPE_USE
}
比如下例,声明的注解只能注解于Java 类型 和 方法
@Target({ElementType.TYPE, ElementType.METHOD})
@interface MyAnnotation {}
请注意,当注解未指定Target值时,则此注解可以用于任何元素之上。
2)@Retention ,用来规定注解的作用时机,该元注解有三种取值,
RetentionPolicy.SOURCE : 注解只存在于源码中,不会存在于.class文件中,在编译时会被忽略掉
RetentionPolicy.CLASS:注解只存在于.class文件中,在编译期有效,但是在运行期会被忽略掉,这也是默认范围
RetentionPolicy.RUNTIME:在运行期有效,JVM在运行期通过反射获得注解信息(源码、class文件和运行时都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {}
上例表示MyAnnotation 注解在运行时有效,JVM会在运行时通过反射机制获取注解信息。
3)@Document ,被修饰的注解会生成到javadoc中,即当使用Java 命令生成JavaDoc 时,使用@Documented元注解定义的注解将会生成到javadoc中, 而没有此元注解的注解则不会在doc文档中出现。
4)@Inherited ,可以让注解被继承,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,如
@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@interface MyAnnotation {}
@MyAnnotation
class ParentClass {}
class ChildClass extends ParentClass {}
因为有了@Inherited 声明,所以ChildClass 也从父类ParentClass 那继承了这个注解。
5)@Repeatable ,JDK1.8新加入的,它表示在同一个位置重复相同的注解。在没有该注解前,一般是无法在同一个类型上使用相同的注解的。
//Java8前无法这样使用
@FilterPath("/web/update")
@FilterPath("/web/add")
public class A {}
下例显示了如何使用@Repeatable 元注解
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(FilterPaths.class) //参数指明接收的注解class
public @interface FilterPath {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface FilterPaths {
FilterPath[] value(); // 接收的注解必须声明的元素
}
通过使用@Repeatable后,将使用@FilterPaths注解作为接收同一个类型上重复注解的容器,而每个@FilterPath则负责保存指定的路径串。
为了处理上述的新增注解,Java8还在AnnotatedElement接口新增了getDeclaredAnnotationsByType() 和 getAnnotationsByType()两个方法并在接口给出了默认实现,在指定@Repeatable的注解时,可以通过这两个方法获取到注解相关信息。
注意,旧版API中的getDeclaredAnnotation() 和 getAnnotation() 是不对@Repeatable注解的处理的(除非该注解没有在同一个声明上重复出现)。
注意,getDeclaredAnnotationsByType方法获取到的注解不包括父类,其实当 getAnnotationsByType()方法调用时,其内部先执行了getDeclaredAnnotationsByType方法,只有当前类不存在指定注解时,getAnnotationsByType()才会继续从其父类寻找,但请注意如果@FilterPath和@FilterPaths没有使用了@Inherited的话,仍然无法获取。
5. 注解元素及其数据值:
在自定义注解中,一般都会包含一些元素以表示某些值,以便后续处理注解时使用。注解中的每个元素定义类似于接口中的方法定义,每个元素定义包含一个数据类型和名称。
@interface MyAnnotation {
String name();
int age();
}
// 使用注解,并给注解元素赋值
@MyAnnotation(name = "whx", age = 18)
public class AnnotationTest {
}
注解中的元素可以设置默认值,通过default 来实现
@interface MyAnnotation {
String name() default "whx";
int age() default 18;
}
当一个元素被设置默认值之后,这个元素便成了注解的可选元素,即在使用注解时如果不为这个元素赋值,将使用默认值。
编译器对元素的默认值有一些限制,首先,元素不能有不确定的值,也就是说,元素必须要么具有默认值,要么在使用注解时提供元素的值。其次,对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值,因为每个注解的声明中,所有的元素都存在,并且都具有相应的值。
注解元素的数据类型:
所有基本类型(int,float,boolean,byte,double,char,long,short)
String
Class
enum
Annotation
上述类型的数组
倘若使用了其他数据类型,编译器将会丢出一个编译错误,注意,声明注解元素时可以使用基本类型但不允许使用任何包装类型,同时还应该注意到注解也可以作为元素的类型,也就是嵌套注解,如下例
@interface MyAnnotation {
enum Gender {MALE, FEMALE}
// 枚举类型
Gender status() default Gender.MALE;
// 常用类型
String name() default "whx";
int age() default 18;
// Class 类型
Class> testCase() default Void.class;
// 注解嵌套
Reference reference() default @Reference(next = true);
// 数组类型
long[] value() default {1, 2, 3};
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Reference {
boolean next() default false;
}
快捷方式:
就是注解中定义了名为value 的元素,并且在使用该注解时,如果该元素是唯一需要赋值的一个元素,那么此时无需使用key=value的语法,而只需在括号内给出value元素所需的值即可。这可以应用于任何合法类型的元素,但是,这限制了元素名必须为value 。
@interface QuickWay {
String value();
}
@QuickWay("hello world")
class QuickTest {
}
6. 常用Java 内置注解:
主要有三个:
@Override:用于标明此方法覆盖了父类的方法, 当我们在子类中覆盖父类的方法时,就要用到@Override注解,这样,如果父类中的方法名称或参数发生改变时,如果子类没有做相应的调整编译器便会报错,这就是@Override注解所起到的作用。
@Deprecated:用于标明已经过时的类、方法、属性、方法参数等,被标记为@Deprecated 的类、方法等在编程中不建议使用,因为后来版本的API 可能将这些过期的去掉。
@SuppressWarnnings:用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。
三. 注解与反射机制
上面对注解做了一个详细介绍,具体该如何使用我们的自定义注解呢?其实在现实应用中,我们的自定义注解一般都是起到运行时指示的作用,也就是运行时注解。对于运行时注解,我们可以通过反射机制获得注解信息。
Java在java.lang.reflect 反射包下新增了AnnotatedElement接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的Constructor类、Field类、Method类、Package类和Class类都实现了AnnotatedElement接口。
下面是AnnotatedElement中相关的API方法:
返回值
方法名称
说明
getAnnotation(Class annotationClass)
该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
Annotation[]
getAnnotations()
返回此元素上存在的所有注解,包括从父类继承的
boolean
isAnnotationPresent(Class extends Annotation> annotationClass)
如果指定类型的注解存在于此元素上,则返回 true,否则返回 false。
Annotation[]
getDeclaredAnnotations()
返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组
下面来看几个示例:
class ATest {
public static void main(String[] args) {
Class> clazz = ChildClass.class;
// 判断类上是否有指定的注解
boolean b = clazz.isAnnotationPresent(CommonAnno.class);
// 根据指定注解类型获取该注解
ClassAnno classAnno = clazz.getAnnotation(ClassAnno.class);
System.out.println(classAnno.name());
// 获取该类上的所有注解,包括从父类继承
Annotation[] anos = clazz.getAnnotations();
System.out.println(Arrays.toString(anos));
// 获取该类上的所有注解,不包括从父类继承
Annotation[] anos2 = clazz.getDeclaredAnnotations();
System.out.println(Arrays.toString(anos2));
try {
// 获取方法上的注解
Method method = clazz.getMethod("add", int.class, int.class);
Annotation[] ans = method.getAnnotations();
System.out.println(Arrays.toString(ans));
// 获取方法参数的注解
Annotation[][] paramAnnos = method.getParameterAnnotations();
Class[] paramTypes = method.getParameterTypes();
int i = 0;
for (Annotation[] annotations : paramAnnos) {
Class paramType = paramTypes[i++];
for (Annotation anno : annotations) {
if (anno instanceof ParamAnno) {
ParamAnno paramAnno = (ParamAnno) anno;
System.out.println(paramType.getName());
System.out.println(paramAnno.name());
}
}
}
// 获取属性的注解
Field field = clazz.getField("dep");
FiledAnno anno = field.getAnnotation(FiledAnno.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
上面只是一些简单的示例,可以通过结合注解和反射实现一些复杂的功能,比如Spring 框架的基于注解的配置等。