注解概述
- 从JDK5.0开始,Java增加了对元数据(MetaData)的支持,也就是注解(Annotation)。
- 注解其实就是代码里的特殊标记,可以理解为标签,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下在原文件嵌入一些补充信息。代码分析工具,开发工具和部署工具可以通过这些补充信息进行验证或进行部署。
- 注解可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在 Annotation 的 name-value 对中。
- 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能、忽略警告等。在 JavaEE / Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 JavaEE 旧版中所遗留的繁冗代码和 XML 配置等。
- 未来的开发模式都是基于注解的,这JPA是基于注解的,Spring2.5 以上都是基于注解的,Hibernate3.x 以后也是基于注解的,现在Struts2 有一部分也是基于注解的。注解是一种趋势,一定程度上可以说 框架=注解+反射+设计模式。
注解示例
使用 Annotation 时要在前面增加 @ 符号,并把 Annotation 当成一个修饰符使用。用于修饰它支持的程序元素。
示例一:生成文档相关的注解
@author:标明开发该类模块的作者,多个作者之间使用 “,” 隔开
@version:标明该类模块的版本
@see:参考转向,也就是相关主题
@since:从哪个版本开始增加的
@param:对方法中某个参数的说明,如果没有参数就不用写
@return:对方法返回值的说明,如果方法的返回值类型是void就不用写
@exception:对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不用写
其中:
@param,@return,@exception这三个标记都是只用于方法的。
@param的格式:@param 形参名 形参类型 形参说明
@return的格式:@return 返回值类型 返回值说明
@exception的格式:@exception 异常类型 异常说明
@param和@exception可以并列多个
示例二:在编译时进行格式检查(JDK的内置的三个基本注解)
@Override:重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类、方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择(仍然可以使用,但尽量不用)
@SuppressWarnings:抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能,极大提高了效率(多用于框架)
Servlet3.0 提供了 @WebServlet(),使得不再需要在web.xml文件中进行Servlet的部署
自定义注解
定义新的Annotation类型用 @interface 关键字,自定义注解会自动继承java.lang.annotation.Annotation接口。
需要注意的是,在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组。
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。可以使用default给成员变量默认值。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
int id();
String name() default "hello";
}
上面代码定义 MyAnnotation 这个注解中拥有 id 和 name 两个属性。在使用的时候,我们应该给它们进行赋值。赋值的方式是在注解的括号内以 value=”” 形式,多个属性用 ,隔开。
@MyAnnotation(id = 3, name = "Tom")
public class Test {}
或者使用默认值
@MyAnnotation(id = 3)
public class Test {}
如果一个注解内仅仅只有一个名字为 value 的属性时,使用这个注解时可以直接将属性写到括号内。
public @interface Check {
String value();
}
下面两种使用方式是等价的
@Check("hi")
int a;
@Check(value="hi")
int a;
还需要注意的一种情况是,一个注解没有任何属性,使用这个注解的时候,括号可以省略。
public @interface Perform {}
@Perform
public void testMethod(){}
JDK中的元注解
JDK的元注解用于修饰其它注解,即注解的注解。JDK中有5个元注解:@Retention,@Target,@Documented,@Inherited,@Repeatable(JDK1.8加入)。
package java.lang.annotation;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
@Retention
用于指定该 Annotation 的生命周期。@Retention包含一个RetentionPolicy类型的成员变量,使用@Retention必须为其value成员变量指定值,如:@Retention(RetentionPolicy.RUNTIME)
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译时会被编译器丢弃。
RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java程序时,JVM不会保留注释。这是默认值。
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时,JVM会保留注释。程序可以通过反射获取该注释。
@Target
用于指定 Annotation 能用于修饰哪些程序元素。@Target也包含一个名为value的成员变量。
package java.lang.annotation;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
ElementType.CONSTRUCTOR:用于描述构造器
ElementType.FIELD:用于描述域
ElementType.LOCAL_VARIABLE:用于描述局部变量
ElementType.METHOD:用于描述方法
ElementType.PACKAGE:用于描述包
ElementType.PARAMETER:用于描述参数
ElementType.TYPE:用于描述类、接口(包括注解类型)、enum声明
@Documented
所修饰的注解在被 javadoc 解析时保留下来。默认情况下,javadoc 是不包括注解的。
定义了 @Documented 的 Retention 值必须为 RUNTIME。
@Inherited
被它修饰的注解具有继承性,如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解。
@Repeatable
被修饰的注解是可重复的,注解的值可以是多个。
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person{
String role default "";
}
@Person(role="artist")
@Person(role="coder") // 赋值
@Person(role="PM")
public class SuperMan{}
注意上面的代码,@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个容器注解,用来存放其它注解。容器注解里面必须要有一个 value 的属性,属性类型是一个被 @Repeatable 注解过的注解数组
反射与注解
注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
通过 getAnnotation() 或者是 getAnnotations() 方法来获取 Annotation 对象。前一种方法返回指定类型的注解,后一种方法返回注解到这个元素上的所有注解。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}
public Annotation[] getAnnotations() {}
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@MyAnnotation(id = 128, name = "HXP")
public class AnnotationTest {
@Deprecated
public static void func() { }
public static void main(String[] args) {
Class<?> clazz = AnnotationTest.class;
boolean hasAnnotation = clazz.isAnnotationPresent(MyAnnotation.class);
if (hasAnnotation) {
MyAnnotation myAnnotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println("id: " + myAnnotation.id());
System.out.println("name: " + myAnnotation.name());
}
try {
Method method = clazz.getDeclaredMethod("func");
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("method annotation: " + annotation.annotationType().getSimpleName());
}
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
}