Java的Annotation注解
Annotation提供了一种为程序元素设置元数据的方法,用于修饰类、成员变量、方法,在编译、类加载、运行时被读取,并执行相应的处理。
Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来取得注解里的元数据。
一、基本Annotaion
1.@Override
指定方法覆盖,强制一个子类必须覆盖父类的方法。
该注解只能修饰方法!
2.@Deprecated
表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器将会给出警告。
3.@SuppressWarning
抑制指定的编译器警告,该注解会一直作用于该程序元素的所有子元素。
注意:使用该注解时要在括号里使用name=value的形式为该Annotation的成员变量设置值。
4.Java7的“堆污染”警告@SafeVarargs
堆污染Heap pollution:当把一个不带泛型的对象赋给一个带泛型的变量时,就会发生堆污染。
List list = new ArrayList<Integer>();
list.add(20);
List<String> ls = list;
System.out.println(ls.get(0));
5.Java8的函数式接口@FunctionalInterface
Java8规定:如果接口中只有一个抽象方法(可以包含多个默认default方法或多个static方法),该接口就是函数式接口。
注意:该注解只能修饰接口!
二、JDK的元Annotation
1.@Retention
用于指定被修饰的Annotation可以保留多长时间,@Retention包含一个RetentionPolicy类型的value成员变量,value成员变量的值只能是如下三个:
RetentionPolicy.CLASS:编译器把Annotation记录在class文件中。当运行Java程序时,JVM不可获取Annotation信息。
RetentionPolicy.RUNTIME: 编译器把Annotation记录在class文件中。当运行Java程序时,JVM也可获取Annotation信息,程序 可以通过反射获取Annotation信息。
RetentionPolicy.SOURCE:Annotation只保留在源代码中,编译器直接丢弃这种Annotation。
@Retention(RetentionPolicy.RUNTIME)
public @interface TestRetention {
}
2.@Target
@Target用于指定被修饰的Annotation能用于修饰那些程序单元。@Target元Annotation包含一个名为value的成员变量,该成员变量的值只能是如下几个:
ElementType.TYPE : 指定该策略的Annotation可以修饰类、接口(包括注解类型)或枚举定义。
ElementType.FIELD : 指定该策略的Annotation只能修饰成员变量。
ElementType.METHOD : 指定该策略的Annotation只能修饰方法定义。
ElementType.PARAMETER : 指定该策略的Annotation可以修饰参数。
ElementType.CONSTRUCTOR : 指定该策略的Annotation只能修饰构造器。
ElementType.LOCAL_VARIABLE : 指定该策略的Annotation只能修饰局部变量。
ElementType.ANNOTATION_TYPE : 指定该策略的Annotation只能修饰Annotation。
ElementType.PACKAGE : 指定该策略的Annotation只能修饰包定义。
@Target(ElementType.TYPE)
public @interface TestTarget {
}
3.@Documented
@Documented用于指定该元Annotation修饰的Annotation类将被javadoc工具提取成文档。
4.@Inherited
@Inherited元Annotation指定被它修饰的Annotation将具有继承性—如果某个类使用了@Xxx注解修饰,则其子类将自动被@Xxx修饰。
三、自定义Annotation
1.定义Annotation
定义新的Annotation类型使用@interface关键字。
所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口。
参数成员只能用public或默认(default)这两个访问权修饰。
参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组。
根据Annotation是否可以包含成员变量,可以分为两类:
- 标记Annotation:没有定义成员变量。
- 元数据Annotation:包含成员变量。
public @interface Test {
}
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果名称注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果颜色注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color{ BLUE,RED,GREEN};
/**
* 颜色属性
*/
Color fruitColor() default Color.GREEN;
}
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 水果供应者注解
*/
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1;
/**
* 供应商名称
*/
public String name() default "";
/**
* 供应商地址
*/
public String address() default "";
}
2.提取Annotation信息
使用注解修饰了类/方法/成员变量等之后,这些注解不会自己生效,必须由这些注解的开发者提供相应的工具来提取并处理注解信息(当然,只有当定义注解时使用了@Retention(RetentionPolicy.RUNTIME)修饰,JVM才会在装载class文件时提取保存在class文件中的注解,该注解才会在运行时可见,这样我们才能够解析).
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。
Java5在java.lang.reflect包下新增了AnnotatedElement接口,代表程序中可以接受注解的程序元素。AnnotatedElement接口的实现类有:Class(类元素)、Field(类的成员变量元素)、Method(类的方法元素)、Package(包元素),每一个实现类代表了一个可以接受注解的程序元素类型。这样, 我们只需要获取到Class、Method、Filed等这些实现了AnnotatedElement接口的类的实例,通过该实例对象调用该类中的方法(AnnotatedElement接口中抽象方法的重写) 就可以获取到我们想要的注解信息了。
获得Class类的实例有三种方法:
(1)利用对象调用getClass()方法获得Class实例
(2)利用Class类的静态的forName()方法,使用类名获得Class实例
(3)运用.class的方式获得Class实例,如:类名.class
AnnotatedElement接口提供的抽象方法(在该接口的实现类中重写了这些方法):
2.1. <T extends Annotation> T getAnnotation(Class<T> annotationClass)
<T extends Annotation>为泛型参数声明,表明A的类型只能是Annotation类型或者是Annotation的子类。
功能:返回该程序元素上存在的、指定类型的注解,如果该类型的注解不存在,则返回null
2.2. Annotation[] getAnnotations()
功能:返回此元素上存在的所有注解,包括没有显示定义在该元素上的注解(继承得到的)。(如果此元素没有注释,则返回长度为零的数组。)
2.3. <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
功能:这是Java8新增的方法,该方法返回直接修饰该程序元素、指定类型的注解(忽略继承的注解)。如果该类型的注解不存在,返回null.
2.4. Annotation[] getDeclaredAnnotations()
功能:返回直接存在于此元素上的所有注解,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)
2.5. boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
功能:判断该程序元素上是否存在指定类型的注解,如果存在则返回true,否则返回false。
2.6. <T extends Annotation> T[] getAnnotationsByTpye(Class<T> annotationClass)
功能: 因为java8增加了重复注解功能,因此需要使用该方法获得修饰该程序元素、指定类型的多个注解。
2.7. <T extends Annotation> T[] getDeclaredAnnotationsByTpye(Class<T> annotationClass)
功能: 因为java8增加了重复注解功能,因此需要使用该方法获得直接修饰该程序元素、指定类型的多个注解。
Class提供了getMethod()、getField()以及getConstructor()方法(还有其他方法),这些方法分别获取与方法、域变量以及构造函数相关的信息,这些方法返回Method、Field 以及Constructor类型的对象。
Class.forName("Test").getMthod("testMethod").getAnnotations();
3.使用Annotation的示例
import test.FruitColor.Color;
/**
* 注解使用
*/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName(){
System.out.println("水果的名字是:苹果");
}
}
import test.FruitColor.Color;
/**
* 注解使用
*/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor=Color.RED)
private String appleColor;
@FruitProvider(id=1,name="陕西红富士集团",address="陕西省西安市延安路89号红富士大厦")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName(){
System.out.println("水果的名字是:苹果");
}
}
4.Java8新增的重复注解
Java8以前,同一个程序元素最多只能使用一个相同类型的Annotation;如果需要在同一个程序元素前使用多个相同类型的Annotation,则必须使用Annotation“容器”。Java8可以将注解改造成重复注解,需要使用@Repeatable修饰该注解,且必须指定value值,该值应该是一个“容器”注解。
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.TYPE})//目标是方法
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
@Repeatable(LogOperations.class)
public @interface LogOperation {
//自定义注解的属性,default是设置默认值
String desc() default "无描述信息";
}
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.TYPE})//目标是方法
@Documented//文档生成时,该注解将被包含在javadoc中,可去掉
public @interface LogOperations {
LogOperation[] value();
}
注意:“容器”注解的保留期必须必它所包含的注解的保留期更长,否则编译器报错。
@LogOperation(desc = "测试1")
@LogOperation(desc = "测试2")
public class LogOperationsTest {
public static void main(String[] args) {
LogOperation[] logOperations = LogOperationsTest.class.getAnnotationsByType(LogOperation.class);
for (LogOperation logOperation : logOperations) {
System.out.println(logOperation.desc());
}
LogOperations container = LogOperationsTest.class.getDeclaredAnnotation(LogOperations.class);
System.out.println(container);
}
//测试1
//测试2
//@com.test.api.log.annotation.LogOperations(value=[@com.test.api.log.annotation.LogOperation(desc=测试1), @com.test.api.log.annotation.LogOperation(desc=测试2)])
}
实际上,重复注解只是一种简化写法,系统仍然将两个@LogOperation注解作为@LogOperations的value成员变量的数组元素。
5.Java8新增的Type Annotation
在 Java 8 之前的版本中,只能允许在声明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:
- 初始化对象时 (new),
- 对象类型转化时,
- 使用 implements 表达式时,
- 使用 throws 表达式时。
清单 1. Type Annotation 使用示例
//初始化对象时
String myString = new @NotNull String();
//对象类型转化时
myString = (@NonNull String) str;
//使用 implements 表达式时
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表达式时
public void validateValues() throws @Critical ValidationFailedException{
...
}
定义一个 Type Annotation 的方法与普通的 Annotation 类似,只需要指定 Target 为 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同时指定这两个 Target。
清单 2. 定义 Type Annotation 示例
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}
ElementType.TYPE_PARAMETER 表示这个 Annotation 可以用在 Type 的声明式前,而 ElementType.TYPE_USE 表示这个 Annotation 可以用在所有使用 Type 的地方(如:泛型,类型转换等)
与 Java 8 之前的 Annotation 类似的是,Type Annotation 也可以通过设置 Retention 在编译后保留在 class 文件中(RetentionPolicy.CLASS)或者运行时可访问(RetentionPolicy.RUNTIME)。但是与之前不同的是,Type Annotation 有两个新的特性:在本地变量上的 Annotation 可以保留在 class 文件中,以及泛型类型可以被保留甚至在运行时被访问。
虽然 Type Annotation 可以保留在 class 文件中,但是它并不会改变程序代码本身的行为。例如在一个方法前加上 Annotation,调用此方法返回的结果和不加 Annotation 的时候一致。
Java 8 通过引入 Type Annotation,使得开发者可以在更多的地方使用 Annotation,从而能够更全面地对代码进行分析以及进行更强的类型检查。
四、编译时处理Annotation
APT(Annotation Processing Tool)时一种注解处理工具,它对源代码文件进行检测,并找出源文件所包含的Annotation信息,然后针对Annotation信息进行额外的处理。
每个Annotation处理器都需要实现javax.annotation.processing包下的Processor接口,通常会继承AbstractProcessor来实现Annotation处理器。

浙公网安备 33010602011771号