Java SE第14章 注解
- 1. 注解的概念和作用
- 2. @Override注解的功能和用法
- 3. @Deprecated注解的功能和用法
- 4. @SuppressWarnings注解的功能和用法
- 5. @Retention注解的功能和用法
- 6. @Target注解的功能和用法
- 7. @Documented注解的功能和用法
- 8. @Inherited注解的功能和用法
- 9. 自定义注解
- 10. 提取注解信息
- 11. 重复注解
- 12. 类型注解
- 13. 使用APT工具
1. 注解的概念和作用
从 JDK 5开始, Java 增加了对元数据的支持,也就是注解(Annotation),这些注解,其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时读取,并执行相应的处理
注解是一个接口,程序可以通过反射获取指定程序元素的注解对象,再通过 java.lang.annotation.Annotation对象获取注解里的元数据
如果希望让程序中的注解在运行时起一定的作用,只有通过某种配套的工具对注解中的信息进行访问和处理,这些工具统称APT
2. @Override注解的功能和用法
限定重写父类方法,只能修饰方法
public class Fruit{
public void info(){};
}
class Apple extends Fruit{
@Override //限定必须重写父类方法,@Override告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则编译会出错
public void info(){
}
}
3. @Deprecated注解的功能和用法
@Deprecated用于表示某个程序元素已过时,当有程序调用已过时的类,方法时,编译器会给出警告
java 为 @Deprecated注解增加了两个属性
- **forRemoval:**该 boolean 类型的属性指定该 API在将来是否会被删除
- since:该 String 类型的属性指定该 API从哪个版本被标记为过时
@Deprecated(since = "9", fomRemoval = true)
public void info(){}
4. @SuppressWarnings注解的功能和用法
@SuppressWarnings指示被该注解修饰的程序元素(以及该程序元素中所有的子元素)取消显示指定的编译器警告
@SuppressWarnings一直会作用于该程序元素的所有子元素
@SuppressWarnings(value = "unchecked")
如果不希望看到堆污染的警告,可以使用 @SafeVarargs修饰引发该警告的方法或构造器,它是 java 7 专门为抑制“堆污染”警告提供的,java 9 增强了该注释,允许使用该注释修饰私有实例方法,或者可以使用 **@SuppressWarnings(“unchecked”)**修饰
@Functionallnterface是用来专门指定某个接口必须是函数式接口(接口中只有一个抽象方法),只能修饰接口
5. @Retention注解的功能和用法
@Retention只能用于修饰注解定义,用于指定被修饰的注解可以保留多长时间,包含一个 RetentionPolicy类型的 value成员变量,使用 @Retention时必须为该 value变量指定值。只能是如下三个
- **RetentionPolicy.CLASS:**编译器把注解记录在 class文件中,当运行java程序时,JVM不可获取注解信息,这是默认值
- **RetentionPolicy.RUNTIME:**编译器把注解记录在class文件中,当运行java程序时,JVM可以获取注解信息,程序可以通过反射获取该注解信息
- **RetentionPolicy.SOURCE:**注解只保留在源代码中,编译器直接丢弃这种注解
为 value指定初始值的两种方法
@Retention(value = RetentionPolicy.RUNTIME) //定义下面的注解保留到运行时
@Retention(RetentionPolicy.SCOURCE) //定义下面的注解会被编译器直接丢弃
6. @Target注解的功能和用法
@Target也只能修饰注解定义,用于指定被修饰的注解能用于修饰哪些程序单元,也包含一个名为value的成员变量,该成员变量的值只能时如下几个
- **ElementType.ANNOTATION_TYPE:**只能修饰注解
- **ElementType.CONSTRUCTOR:**修饰构造器
- **ElementType.FIELD:**修饰成员变量
- **ElementType.LOCAL_VARIABLE:**局部变量
- **ElementType.METHOD:**方法
- **ElementType.PACKAGE:**包
- ElementType.PARAMETER:参数
- ElementType.TYPE:可以修饰类,接口(包括注释类型),枚举
可以直接指定value值,无需使用 name = value形式
7. @Documented注解的功能和用法
@Documented用于指定被该元注释修饰的注解类将被javadoc工具提取成文档
8. @Inherited注解的功能和用法
@Inherited注解指定被它修饰的注解具有继承性:如果一个类使用了@Xxx注解(定义该注解时使用了@Inherited修饰)修饰,则其子类自动被@Xxx修饰
9. 自定义注解
定义新的注解时使用 @interface关键字
public @interface Test{
}
默认情况下,注解可以修饰任何程序元素
注解还可以带成员变量,成员变量在注解中以无形参的方法形式声明,其方法名和返回值定义了该成员变量的名字和类型
public @interface MyTag{
String name();
int age();
}
使用 @interface定义的注解像是定义了一个注解接口,这个注解接口继承了java.lang.annotation.Annotation接口,这一点可以通过反射看到MyTag接口里包含了java.lang.annotation.Annotation接口里的方法
使用带成员变量的注解时,需要为成员变量赋值
@MyTag(name = "xx", age = 6)
public void info(){}
也可以在定义注解的成员变量时为其指定初始值,需要使用default
public @interface MyTag{
String name() default "我";
int age() default 32;
}
如果指定了默认值,使用时就可以不赋初始值
根据注解是否可以包含成员变量,可以把注解分为如下两类
- 标记注解:没有定义成员变量的注解类型
- 元数据注解:包含成员变量的注解
10. 提取注解信息
使用注解修饰了类,方法,成员变量之后,这些注解不会自己生效,必须由开发者提供相应的工具来提取并处理注解信息
java 5在 java.lang.reflect包下新增了 AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类
- Class:类定义
- Constructor:构造器定义
- Field:类成员变量定义
- Method:类的方法定义
- Package:类的包定义
java.lang.reflact包下主要包含一些实现反射功能的工具类,从 java 5开始,java.lang.reflact包所提供的反射 API增加了读取运行时注解的能力,只有当定义注解时使用了 **@Retention(RetentionPolicy.RUNTIME)**修饰,该注解才会在运行时可见,JVM才会在装载 *.class文件时读取保存在 class文件中的注解信息
AnnotatedElement接口是所有程序元素的父接口,当程序通过反射获取了某个类的 AnnotatedElement对象之后,程序就可以调用该对象的如下几个方法来访问注解信息
- ** A getAnnotation(Class annotationClass):**返回该程序元素上存在的指定类型的注释,如果不存在返回null
- ** A getDeclaredAnnotation(Class annotationClass):**Java 8 新增方法,该方法尝试获取直接修饰该程序元素,指定类型的注解,不存在返回null
- **Annotation[] getAnnotations():**返回该程序元素上所有的注解
- **Annotation[] getDeclaredAnnotations():**返回直接修饰该程序元素的所有注解
- **boolean isAnnotationPresent(Class<? extends Annotation> annotationClass):**判断该程序元素上是否存在指定类型的注解,存在返回true,否则false
- **A extends Annotation> A[] getAnnotationsByType(Class annotationClass):**使用该方法获取修饰该程序元素,指定类型的多个注解
- ** A[] getDeclaredAnnotationsByType(Class annotationClass):**使用该方法获取直接修饰该程序元素,指定类型的多个注解
//获取Test类的info方法的所有注解
Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations();
//遍历所有注解
for(Annotation an : aArray){
System.out.println(an);
}
如果 需要获取某个注解里的元数据,可以将注解强制类型转换为所需的注解类型,然后通过注解对象的抽象方法来访问这些元数据
//获取tt对象的info方法所包含的所有注解
Annotation[] annotation = tt.getClass().getMethod("info").getAnnotations();
//遍历每个注解对象
for(Annotation tag : annotation){
//如果 tag是 MyTag1类型
if(tag instanceof MyTag1){
//将 tag强制转换为MyTag1
//输出 tag对象的method1成员变量的值
System.out.println( ((MyTag1)tag).method1 );
}
}
@Testable没有任何成员变量,仅是一个标记注解,它的作用是标记哪些方法是可测试的
//使用 @Retention指定注解保留到运行时
@Retention(RetentionPolicy.RUNTIME);
//使用 @Target指定被修饰的注解可用于修饰方法
@Target(ElementType.METHOD);
public @interface Testable{
}
11. 重复注解
java 8 之前,同一个程序元素前最多只能使用一个相同类型的注释,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”,从 Java 8 开始,允许使用多个相同类型的注解来修饰同一个类
开发重复注解需要使用 @Repeatable修饰
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FKTag{
String name() default "默认值";
int age();
}
如果使用两个以上该注解修饰同一个类,编译器会报错
所以必须使用 @Repeatable修饰该注解,首先必须给value成员变量指定值,该成员变量的值应该是一个“容器”注解,该容器注解可包含多个@FKTag,因此还需要定义如下“容器”注释
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FKTags{
//定义value成员变量,该成员变量可接受多个 @FKTage注释
FKTag[] value();
}
注意:“容器”注解的保留期必须比它所包含的注解的保留期更长,否则编译器会报错
接下来程序可在定义 @FKTag注解时添加如下修饰代码
@Repeatable(FKTags.class)
现在就已经定义了一个重复注解@FKTage
现在可以有两种方式使用重复注解
1.
@FKTags({@FkTag(age = 5), @FKTag(name = "aa", age = 2)})
2.
@FKTag(age = 5)
@FKTag(name = "aa", age = 2)
实际上第二种用法只是一个简化写法,系统依然将两个@FKTag注解作为@FKTags的value成员变量的数组元素
重复注释只是一种简化写法,这种简化写法是一种假象:多个重复注解其实会被作为“容器”注解的value成员变量的数组元素
12. 类型注解
Java 8为 ELementType枚举增加了 TYPE_PARAMETER,TYPE_USE两个枚举值,这样就允许定义枚举时使用@Target(ElementType.TYPE_USE)修饰,这种注解被称为类型注解 (Type Annotation),类型注解可用于修饰在任何地方出现的类型
从 Java 8开始,允许在如下位置使用类型注解
- 创建对象(用 new 关键字创建)
- 类型转换
- 使用 implements实现接口
- 使用 throws 声明抛出异常
//定义一个简单的类型注解
@Target(ElementType.TYPE_USE)
@interface NotNull{}
//定义类时使用类型注解
@NotNull
public class TypeAnnotationTest implements @NotNull/*implements时使用类型注解*/ Serializable{
//方法形参中使用类型注解
public static void main(@NotNUll String[] args)
//throws时使用类型注解
throws @NotNull FileNotFoundException
{
Object obj = "das";
//强制类型转换时使用类型注解
String str = (@NotNull String)obj;
//创建对象时使用类型注解
Object win = new @NotNull JFrame("das");
}
//泛型中使用类型注解
public void foo(List<@NotNull String> info){
}
}
此时虽然程序大量使用了@NotNull注解,但这些注解暂时不会起任何作用,因为并没有为这些注解提供处理工具,而且 Java 8本身并没有提供对类型注解执行检查的框架
13. 使用APT工具
APT是一种注解处理工具,它对源代码文件进行检测,并找出源文件所包含的注解信息,然后针对注解信息进行额外的处理
使用APT工具处理注解时可以根据源代码中的注解生成额外的源文件和其他的文件,APT还会编译生成的源代码文件和原来的源文件,将它们一起生成class文件
Java提供的 javac.exe工具有一个 -processor选项,可以指定一个注解处理器,如果指定了一个注解处理器,那么这个注解处理器在编译时提取并处理Java源文件中的注解
每个注解处理器都需要实现 javax.annotation.processing包下的Processor接口,不过实现这个接口必须实现里面所有方法,所以一般采用继承 AbstractProcessor的方式来实现注解处理器,一个注解处理器可以处理一种或多种注解
下面示范使用APT根据源文件中的注解来生成额外文件
package test.annotation;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.annotation.*;
import java.util.Set;
//该工具的功能是根据注解来生成一个 Hibernate映射文件(也就是根据这些注解生成另一份 XML文件)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//指定可处理三个注解
@SupportedAnnotationTypes({"Persistent", "Id", "Property"})
public class APT_ extends AbstractProcessor {
//循环处理每个需要处理的程序对象
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//定义一个文件输出流,用于生成额外的文件
PrintStream ps = null;
try{
//遍历每个被@Persistent修饰的class文件
for(Element t : roundEnv.getElementsAnnotatedWith(Persistent.class)){//根据注解获取需要处理的程序单元
//获取正在处理的类名
Name clazzName = t.getSimpleName();
//获取类定义前的@Persistent注解
Persistent per = t.getAnnotation(Persistent.class);
//创建文件输出流
ps = new PrintStream(new FileOutputStream(clazzName + ".hbm.xml"));
//执行输出
for(Element f : t.getEnclosedElements()){ //获取该Element里定义的所有程序单元,包括成员变量,方法,构造器,内部类
//只处理成员变量上的注释
if(f.getKind() == ElementKind.FIELD){ //getKind()返回Element所代表的程序单元,可以是ElementKind.Class等
//获取成员变量定义前的@Id注解
Id id = f.getAnnotation(Id.class);
//当 @Id注解存在时输出<id../>元素
if(id != null){
ps.println(f.getSimpleName() + id.column() + id.type());
}
//获取成员变量定义前的@Property注解
Property p = f.getAnnotation(Property.class);
if(p != null){
//同上
}
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
finally{
if(ps != null){
ps.close();
}
}
return true;
}
//定义三种注解类型
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Documented
@interface Persistent{
String table();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
@interface Property{
String column();
String type();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
@Documented
@interface Id{
String column();
String type();
String generator();
}
//定义了三个注解之后,提供一个简单的java类文件,这个Java类文件使用这三个注解来修饰
@Persistent(table = "perosn_inf")
class Person{
@Id(column = "perosn_id", type = "integer", generator = "identity")
int id;
@Property(column = "person_name", type = "String")
String name;
@Property(column = "person_age", type = "integer")
int age;
public Person(){}
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
接下来就可使用带 -processor选项的javac.exe命令来编译Perosn.java了
rem 使用APT_作为 APT处理 Person.java中的注释
javac -processor HibernateAnnotationProcessor Person.java
编译Person.java之后,将可以看到在相同路径下生成了一个 Person.hbm.xml文件