因为本人研究spring框架源码需要,在这里总结一下java1.5特性之一 注解,并围绕什么是注解,为什么要使用注解和注解怎么使用三个问题展开,首先我们说说什么是注解
1,注解是什么?
从java1.5版本之后,就有了这个注解的概念,
注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式,一种描述数据的数据。使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Annotation的类型检查,使得在编译期间就可以排除语法错误。
Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。
元数据的概念:
元数据从metadata一词译来,就是“关于数据的数据”的意思。
元数据的功能作用有很多,比如:你可能用过Javadoc的注释自动生成文档。这就是元数据功能的一种。总的来说,元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。如果要对于元数据的作用进行分类,目前还没有明确的定义,不过我们可以根据它所起的作用,大致可分为三类:
1. 编写文档:通过代码里标识的元数据生成文档
2. 代码分析:通过代码里标识的元数据对代码进行分析
3. 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查
在Java中元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或针在运行时知道被运行代码的描述信息。
综上所述:
第一,元数据以标签的形式存在于Java代码中。
第二,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。
第三,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件。
第四,元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。
注解的分类:
根据注解参数的个数,我们可以将注解分为三类:
1.标记注解:一个没有成员定义的Annotation类型被称为标记注解。这种Annotation类型仅使用自身的存在与否来为我们提供信息。比如后面的系统注解@Override;
2.单值注解
3.完整注解
根据注解使用方法和用途,我们可以将Annotation分为三类:
1.JDK内置系统注解
2.元注解
3.自定义注解
2,为什么要使用注解?
既然是为什么要使用注解,首先我们必须得知道注解的作用了
注解的作用有如下功能:
1.生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等;
2.跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数 量;
3.在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出;
3,怎么使用注解呢,接下来我们需要详细知道注解的使用了
3.1 先说说我们常见的注解,也是jdk1.5内置的三个注解,这三个注解放在java.lang包下,在程序中或多或少都看见过,没了解过的也就不知道它们是干嘛使的了
@Override:
这个注解常用在继承类或实现接口的子类方法上,表面该方法是子类覆盖父类的方法,该方法的方法签名要遵循覆盖方法的原则:即访问控制权限必能比父类更严格,不能比父类抛出更多的异常。
@Deprecated:
这个注解告诉编译器该元素是过时的,即在目前的JDK版本中已经有新的元素代替该元素。
@SuppressWarnings:
该注解关闭编译器中不合适的警告,即强行压制编译器的警告提示。
3.2 再介绍一下内置的四个元注解是什么,分别的作用是哪些
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
使用实例:
//用于描述类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
public @interface DbTable {
public String tableName() default "";
}
//用于描述域
@Target(ElementType.FIELD)
public @interface DbColumn {
public String columnName();
}
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例如下:
//用于描述类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
//用于指定注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {
public String tableName() default "";
}
@Documented:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
//用于描述类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
//用于指定注解的生命周期
@Retention(RetentionPolicy.RUNTIME)
//用于描述其它类型的annotation应该被作为被标注的程序成员的公共API
@Documented
public @interface DbTable {
public String tableName() default "";
}
@Inherited:
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
实例代码:
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
3.3如何自定义注解使用
自定义注解:
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面给的例子就能看出效果
注解元素的默认值:
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。例如:
这是我简单定义的一个注解,名字叫UserName ,其中参数名称使用的value,待会就能看出来特效
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserName {
//这里的参数名称就是value,再使用注解的时候直接赋值就可以了不用通过name=value的形式
public String value() default "Tom";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAge {
//这里咱们定义了一个普通类型的参数
public int UserAge();
}
public class User {
//由于UserName注解中配置的参数名称是value ,所以在这里我们直接传值就行
@UserName("Alice")
private String username;
@UserAge(UserAge=10)
private int userage;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
}
到这里,我们已经会添加自定义的注解了,但是添加之后是怎么给属性赋值的呢?到现在是不是看不出来它与注释的区别?,接下来,我们需要了解的是用来处理这些注解的注解处理器了
在这里,我们就需要用到jdk1.5扩展之后的反射API了,这里会运用到反射机制,接下来我们详细看看怎么创建一个注解处理器
注解处理器类库(java.lang.reflect.AnnotatedElement):
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
Class:类定义
Constructor:构造器定义
Field:累的成员变量定义
Method:类的方法定义
Package:类的包定义
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class
/*
* 人员姓名注解
* @author:fth
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserName {
public String value() default "Tom";
}
/*
* 人员年龄注解
* @author:fth
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAge {
public int UserAge();
}
/*
* 人员公司注解
* @author:fth
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Company {
public int id() default 1;
public String companyName();
public String companyAddress();
}
/*
* 简单的javaBean
*/
public class Man {
@UserName("嘻嘻嘻")
private String username;
@UserAge(UserAge=23)
private int userage;
@Company(id=1,companyName="嘻嘻哈哈公司",companyAddress="北京市")
private String company;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getUserage() {
return userage;
}
public void setUserage(int userage) {
this.userage = userage;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
}
/*
* 注解处理器
*/
public class ManProcessor {
public static void getManInfo(Class<?> classname){
String strUserName="此人的名称:";
String strUserAge="此人的年龄:";
String strCompany="此人的公司:";
Field[] fields = classname.getDeclaredFields();
for(Field field:fields){
//如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。
if(field.isAnnotationPresent(UserName.class)){
UserName usrename = (UserName)field.getAnnotation(UserName.class);
strUserName=strUserName+usrename.value();
System.out.println(strUserName);
}
else if(field.isAnnotationPresent(UserAge.class)){
UserAge userage = (UserAge)field.getAnnotation(UserAge.class);
strUserAge=strUserAge+userage.UserAge();
System.out.println(strUserAge);
}
else if(field.isAnnotationPresent(Company.class)){
Company company = (Company)field.getAnnotation(Company.class);
strCompany=strCompany+company.id()+" 名称:"+company.companyName()+" 地址:"+company.companyAddress();
System.out.println(strCompany);
}
}
}
}
public class Test {
public static void main(String[] args) {
ManProcessor.getManInfo(Man.class);
}
}
-------------------------------------------
此人的名称:嘻嘻嘻
此人的年龄:23
此人的公司:1 名称:嘻嘻哈哈公司 地址:北京市
这样,一个简单的注解处理器就已经创建好了,这样我们应该大致明白是通过注解处理器来处理注解上配置的一些信息了,这是我们自定义的注解以及注解处理器,同时jdk也使用apt( sun的mirror API )外部工具来解析注解,在这里就不详细介绍了,有兴趣可以自己了解了解
相关博客:http://blog.csdn.net/chjttony/article/details/7017153
http://www.cnblogs.com/peida/archive/2013/04/26/3038503.html