一、概念
Annontation是Java5开始引入的新特征。中文名称一般叫注解,也叫元注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。
更通俗的意思是为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且是供指定的工具或框架使用的。
Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中
注解的语法比较简单,除了@符号的使用之外,它基本与Java固有语法一致。
一般是不需要自己去定义注解的,除非你要自己写框架类的东西,如果是,注解是配合反射一起用的,通过反射,可以根据class、field、method等对象拿到它上面标注的注解,然后根据有没有注解、注解的类型或注解上的参数的不同,来执行不同的操作。
二、原理
Annotation其实是一种接口。通过Java的反射机制相关的API来访问annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。
annotation是不会影响程序代码的执行,无论annotation怎么变化,代码都始终如一地执行。
Java语言解释器在工作时会忽略这些annotation,因此在JVM 中这些annotation是“不起作用”的,只能通过配套的工具才能对这些annontaion类型的信息进行访问和处理。
Annotation与interface的异同:
1)、Annotation类型使用关键字@interface而不是interface。
这个关键字声明隐含了一个信息:它是继承了java.lang.annotation.Annotation接口,并非声明了一个interface
2)、Annotation类型、方法定义是独特的、受限制的。
Annotation 类型的方法必须声明为无参数、无异常抛出的。这些方法定义了annotation的成员:方法名成为了成员名,而方法返回值成为了成员的类型。而方法返回值类型必须为primitive类型、Class类型、枚举类型、annotation类型或者由前面类型之一作为元素的一维数组。方法的后面可以使用 default和一个默认数值来声明成员的默认值,null不能作为成员默认值,这与我们在非annotation类型中定义方法有很大不同。
Annotation类型和它的方法不能使用annotation类型的参数、成员不能是generic。只有返回值类型是Class的方法可以在annotation类型中使用generic,因为此方法能够用类转换将各种类型转换为Class。
3)、Annotation类型又与接口有着近似之处。
它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承。
三、应用场合
annotation一般作为一种辅助途径,应用在软件框架或工具中,在这些工具类中根据不同的 annontation注解信息采取不同的处理过程或改变相应程序元素(类、方法及成员变量等)的行为。
例如:Junit、Struts、Spring等流行工具框架中均广泛使用了annontion。使代码的灵活性大提高。
1.1 知识点
1.1.1 Java内置注解组成
注解的语法比较简单,除了@符号的使用以外,它基本上与java的固有语法一致,java内置了三种注解,定义在java.lang包中。
注解名称 | 描述 |
@Override | 表示当前的方法定义将覆盖超类中的方法。 |
@Deprecated | 使用了注解为它的元素编译器将发出警告,因为注解@Deprecated是不赞成使用的代码,被弃用的代码。 |
@SuppressWarnings | 关闭不当编译器警告信息。 它可以有以下参数: deprecation:过时的类或方法警告。 unchecked:执行了未检查的转换时警告。 fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告。 path:在类路径、源文件路径等中有不存在的路径时的警告。 serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告。 finally:任何 finally 子句不能完成时的警告。 all:关于以上所有情况的警告。 |
1.1.2 如何使用注解
注解的实例代码如下:
public class Dog { /** * 一个普通的方法,让此方法变成弃用方法 */ @Deprecated public void } /** * 方法里有未使用的变量i,在eclipse中有警告提示, * 如果我们想在警告去掉,可以使用@SuppressWarnings注解。 * */ @SuppressWarnings({"all"}) public void eat() { int i = 5; } /** * 重写Object类中的toString()方法,如果不加@Override, * 本意是想重写toString(),但如果方法名称写错了也不会报错, * eclipse认为这是在子类里添加了一个新方法,如果加上@Override, * 這就是重寫,父類中必須有這樣的方法。 */ @Override public String toString() { return super.toString(); } } |
1.1.3 创建新注解
和创建一个自定义的类一样,我们不仅可以使用别人写好的类,我们也可以创建自已需要的类,注解也一样,也可以创建系统中不存在的注解。在进行创建新注解时,需要用到原注解,原注解是专门负责新注解的创建的。
原注解共有四个,如下描述:
@Retention
它是被定义在一个注解类的前面,用来说明该注解的生命周期。
它有以下参数:
RetentionPolicy.SOURCE :指定注解只保留在一个源文件当中。
RetentionPolicy.CLASS :指定注解只保留在一个 class 文件中。
RetentionPolicy.RUNTIME :指定注解可以保留在程序运行期间。
@Target
它是被定义在一个注解类的前面,用来说明该注解可以被声明在哪些元素前。
它有以下参数:
ElementType.TYPE :说明该注解只能被声明在一个类前。
ElementType.FIELD :说明该注解只能被声明在一个类的字段前。
ElementType.METHOD :说明该注解只能被声明在一个类的方法前。
ElementType.PARAMETER :说明该注解只能被声明在一个方法参数前。
ElementType.CONSTRUCTOR :说明该注解只能声明在一个类的构造方法前 。
ElementType.LOCAL_VARIABLE :说明该注解只能声明在一个局部变量前。
ElementType.ANNOTATION_TYPE :说明该注解只能声明在一个注解类型前 。
ElementType.PACKAGE :说明该注解只能声明在一个包名前。
@Documented
将注解包含在Javadoc中。
@Inherited
允许子类继承父类中的注解。
先来看一下如何编写一个最简单的注解类:
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS) @Target({ElementType.FIELD, ElementType.METHOD}) @Documented @Inherited public @interface Fruit {
} |
通过此注解返回给我们的信息有:
² 当在一个注解类前定义了一个 @Retetion(RetentionPolicy.CLASS) 的注解,那么说明该注解只保留在一个 class 文件当中,当加载 class 文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。
² 说明该注解只能被声明在一个类的属性和方法前。注意,如果注解类同时可以用到类和方法前,需要用“{}”引起来编写。
² 在生成文档时将注解包含在Javadoc中。
² 允许子类继承父类中的注解。
除了@符号,注解很像是一个接口。定义注解的时候需要用到元注解,上面用到了@Target、@RetentionPolicy、@Documented、@Inherited四个原注解。
1.1.4 注解定义
1、注解可以有哪些成员?
注解和接口相似,它只能定义 final 静态属性和公共抽象方法。
2、注解的方法?
1. 方法前默认会加上 public abstract
2. 在声明方法时可以定义方法的默认返回值。
例如 :
String color() default "blue";
String[] color() default {"blue", "red",......}
3、方法的返回值可以有哪些类型 ?
八种基本类型, String 、 Class 、枚举、注解及这些类型的数组。
1.1.5 注解生命周期
一个注解可以有三个生命周期,它默认的生命周期是保留在一个 CLASS 文件,但它也可以由一个@Retetion的元注解指定它的生命周期。
l java 源文件
当在一个注解类前定义了一个@Retetion(RetentionPolicy.SOURCE) 的注解,那么说明该注解只保留在一个源文件当中,当编译器将源文件编译成 class 文件时,它不会将源文件中定义的注解保留在 class 文件中。
l class 文件中
当在一个注解类前定义了一个 @Retetion(RetentionPolicy.CLASS) 的注解,那么说明该注解只保留在一个 class 文件当中,当加载 class 文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。
l 程序运行期间
当在一个注解类前定义了一个 @Retetion(RetentionPolicy.RUNTIME) 的注解,那么说明该注解在程序运行期间都会存在内存当中。此时,我们可以通过反射来获得定义在某个类上的所有注解。
1.1.6 注解的使用
在注解中一般会有一些元素以表示某些值。注解的元素看起来就像接口的方法,唯一的区别在于可以为其制定默认值。没有元素的注解称为标记注解,上面的@Fruit就是一个标记注解。
注解的可用的类型包括以下几种:所有基本类型、String、Class、enum、Annotation、以上类型的数组形式。
对注解元素的要求:
ü 元素不能有不确定的值,即要么有默认值,要么在使用注解的时候提供元素的值。
ü 元素不能使用null作为默认值。
ü 注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。
ü 注解在只有一个元素且该元素的名称是value的情况下,在使用注解的时候可以省略“value=”,直接写需要的值即可。
继续使用刚才的注解类,给里面添加注解元素,一共添加三个,一个是常量元素,两个是普通元素,两个普通元素一个有默认值,一个没有默认值。
import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
/** * 注解中有三个元素,一个是常量id,一个是sheel,一个是share,share有默认值。 * @author Administrator * */ @Retention(RetentionPolicy.CLASS) @Target({ElementType.TYPE, ElementType.METHOD}) @Documented @Inherited public @interface Fruit { public final int count = 1; public int sheel(); public String share() default "圆"; } |
定义了注解,必然要去使用注解。对@Fruit类进行的使用:
@Fruit(sheel=1) public class Apple {
} |
因为在编写@Fruit时没有给sheel默认值,所以在使用@Fruit必须要给此元素赋值。有默认值的,我们可以对其进行修改:
@Fruit(sheel=1,share="扁") public class Apple {
} |
注意:常量不可以进行修改。
使用注解最主要的部分在于对注解的处理,那么就会涉及到注解处理器。从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。