深入浅析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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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();
                    }
                }
            }
        }
    }
}

  运行结果:

1
2
3
4
获取到方法上的注解值:red
获取到方法上的注解值:2
获取到字段上的注解值:北京市海淀区
获取到字段上的注解值:110