深入浅析JAVA注解
注解,相信大家都会知道,像@requestMapping,@Resource,@Controller等等的一些注解,大家都用过,那么,他的工具类你用过吗?下面就和大家一起来分享一下注解工具类。
注解的作用:
1、生成文档。这是最常见的,也是Java 最早提供的注解。常用的有@see @param @return 等
2、跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量。以后java的程序开发,最多的也将实现注解配置,具有很大用处;
3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
注解的分类:
根据注解使用方法和用途,我们可以将Annotation分为三类:
1.JDK内置系统注解
2.元注解
3.自定义注解
系统内置标准注解:
注解的语法比较简单,除了@符号的使用外,他基本与Java固有的语法一致,Java中内置三个标准注解,定义在java.lang中:
@Override:用于修饰此方法覆盖了父类的方法;
@Deprecated:用于修饰已经过时的方法;
@SuppressWarnnings:用于通知java编译器禁止特定的编译警告。
下面我们依次看看三个内置标准注解的作用和使用场景。
@Override,限定重写父类方法:
@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而又写错了方法名时发挥威力。使用方法极其简单:在使用此annotation时只要在被修饰的方法前面加上@Override即可。@Override注解只能用于方法,不能用于其他程序元素。
@Deprecated,标记已过时:
同 样Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的 “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为 @Deprecated,但编译器仍然要报警。
值得注意,@Deprecated这个annotation类型和javadoc中的 @deprecated这个tag是有区别的:前者是java编译器识别的,而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)。
在java5.0,java编译器仍然像其从前版本那样寻找@deprecated这个javadoc tag,并使用它们产生警告信息。但是这种状况将在后续版本中改变,我们应在现在就开始使用@Deprecated来修饰过时的方法而不是 @deprecated javadoc tag。
@SuppressWarnnings,抑制编译器警告:
@SuppressWarnings
被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。在java5.0,sun提供的javac编译器为我们提供了-Xlint选项来使编译器对合法的程序代码提出警告,此种警告从某种程度上代表了程序错误。例如当我们使用一个generic collection类而又没有提供它的类型时,编译器将提示出"unchecked
warning"的警告。通常当这种情况发生时,我们就需要查找引起警告的代码。如果它真的表示错误,我们就需要纠正它。例如如果警告信息表明我们代码中的switch语句没有覆盖所有可能的case,那么我们就应增加一个默认的case来避免这种警告。
有时我们无法避免这种警告,例如,我们使用必须和非generic的旧代码交互的generic collection类时,我们不能避免这个unchecked warning。此时@SuppressWarning就要派上用场了,在调用的方法前增加@SuppressWarnings修饰,告诉编译器停止对此方法的警告。
@SuppressWarning不是一个标记注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
SuppressWarnings注解的常见参数值的简单说明:
1.
deprecation:使用了不赞成使用的类或方法时的警告;
2. unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
3. fallthrough:当switch程序块直接通往下一种情况而没有 Break 时的警告;
4. path:在类路径、源文件路径等中有不存在的路径时的警告;
5. serial:当在可序列化的类上缺少serialVersionUID定义时的警告;
6. finally:任何finally子句不能正常完成时的警告;
7. all:关于以上所有情况的警告。
以下是JDK内置系统注解的一个简单应用:
元注解:
元注解的作用就是负责注解其他注解。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声明
@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的枚举类型值。Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
@Documented:
@Documented用于描述其它的annotation类型应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@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类型被发现,或者到达类继承结构的顶层。
自定义注解:
使用@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",后加小括号。例如下面的例子FruitName注解就只有一个参数成员。
以下还有一个通过反射机制来获取注解值的一个实现可以供大家来深入的学习注解以及理解JAVA中的反射机制
首先是是自定义注解HelloAnnotation2.java
package Test; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER}) public @interface HelloAnnotation2 { String color() default "red"; String age(); }
HelloAnnotation3.java
package Test; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.FIELD}) public @interface HelloAnnotation3 { String getAddress(); String tel() default "110"; }
测试类 AnnotationTest.java
package Test; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * * 功能:测试Annotation * 时间:2016年12月12日 上午9:07:28 * 作者:茹子赫 */ public class AnnotationTest { @HelloAnnotation2(age="2") public static String testColor(String color){ System.out.println(color); return color; } @HelloAnnotation3(getAddress="北京市海淀区") String address; public static void main(String[] args) { //获取方法上的注解值 Method[] methods = AnnotationTest.class.getDeclaredMethods(); if(methods != null){ for (Method method : methods) { HelloAnnotation2 anntotion2 = method.getAnnotation(HelloAnnotation2.class); if(anntotion2 == null){ continue; } Method[] me = anntotion2.annotationType().getDeclaredMethods(); for (Method meth : me) { try { String color = (String) meth.invoke(anntotion2, null); System.out.println("获取到方法上的注解值:"+color); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } //获取字段上的注解值 AnnotationTest noon = new AnnotationTest(); Field[] field = AnnotationTest.class.getDeclaredFields(); if(field != null){ for (Field fie : field) { if(!fie.isAccessible()){ fie.setAccessible(true); } HelloAnnotation3 annon = fie.getAnnotation(HelloAnnotation3.class); Method[] meth = annon.annotationType().getDeclaredMethods(); for (Method me : meth) { if(!me.isAccessible()){ me.setAccessible(true); } try { fie.set(noon, me.invoke(annon, null)); System.out.println("获取到字段上的注解值:"+fie.get(noon)); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }
运行结果:
获取到方法上的注解值:red 获取到方法上的注解值:2 获取到字段上的注解值:北京市海淀区 获取到字段上的注解值:110