Java自定义Annotation,通过反射解析Annotation

澳门新葡亰3522平台游戏 4

创建一个自定义的Annotation

import java.lang.annotation.*;
import java.lang.reflect.Method;

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodInfo {
    String author() default "hupeng";
    String version() default "1.0";
    String date();
    String comment();

}
  • Annotation methods can’t have parameters.
  • Annotation methods return types are limited to primitives, String,
    Enums, Annotation or array of these.
  • Annotation methods can have default values.

摘要


Java Annotation是JDK5.0引入的一种注释机制。

网上很多关于Java Annotation的文章,看得人眼花缭乱。Java
Annotation本来很简单的,结果说的人没说清楚;弄的看的人更加迷糊。

我按照自己的思路,对Annotation进行了整理。理解 Annotation
的关键,是理解Annotation的语法和用法,对这些内容,我都进行了详细说明;理解Annotation的语法和用法之后,再看Annotation的框架图,可能有更深刻体会。废话就说这么多,下面开始对Annotation进行说明。若您发现文章中存在错误或不足的地方,希望您能指出!

 

@Target:

@Target说明了Annotation所修饰的对象范围:Annotation可被用于
packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:

1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
澳门新葡亰3522平台游戏,5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

第1部分 Annotation架构


先看看Annotation的架构图:

澳门新葡亰3522平台游戏 1

从中,我们可以看出:

(01) 1个Annotation 和 1个RetentionPolicy关联。
     
 可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。

(02) 1个Annotation 和 1~n个ElementType关联。
     
 可以理解为:对于每1个Annotation对象,可以有若干个ElementType属性。

(03) Annotation 有许多实现类,包括:Deprecated, Documented, Inherited,
Override等等。
       Annotation
的每一个实现类,都“和1个RetentionPolicy关联”并且“和1~n个ElementType关联”。

下面,我先介绍框架图的左半边(如下图),即Annotation, RetentionPolicy,
ElementType;然后在就Annotation的实现类进行举例说明。

澳门新葡亰3522平台游戏 2

 

@Retention:

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对
Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:

1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)

第2部分 Annotation组成部分


@Documented:

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

1 annotation组成成分

java annotation 的组成中,有3个非常重要的主干类。它们分别是:

(01) Annotation.java

package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

(02) ElementType.java

package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

(03) RetentionPolicy.java

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

说明:

(01) Annotation 就是个接口。
      “每1个Annotation” 都与 “1个RetentionPolicy”关联,并且与
“1~n个ElementType”关联。可以通俗的理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性;至于ElementType属性,则有1~n个。

(02) ElementType 是Enum枚举类型,它用来指定Annotation的类型。
      “每1个Annotation” 都与
“1~n个ElementType”关联。当Annotation与某个ElementType关联时,就意味着:Annotation有了某种用途。
     
例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法。

(03) RetentionPolicy
是Enum枚举类型,它用来指定Annotation的策略。通俗点说,就是不同RetentionPolicy类型的Annotation的作用域不同。
      “每1个Annotation” 都与 “1个RetentionPolicy”关联。
      a) 若Annotation的类型为
SOURCE,则意味着:Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了。
          例如,“ @Override
”标志就是一个Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,“@Override”就没有任何作用了。
      b) 若Annotation的类型为
CLASS,则意味着:编译器将Annotation存储于类对应的.class文件中,它是Annotation的默认行为。
      c) 若Annotation的类型为
RUNTIME,则意味着:编译器将Annotation存储于class文件中,并且可由JVM读入。

这时,只需要记住“每1个Annotation” 都与 “1个RetentionPolicy”关联,并且与
“1~n个ElementType”关联
。学完后面的内容之后,再回头看这些内容,会更容易理解。

 

@Inherited:

@Inherited
元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。,则这个annotation将被用于该class的子类。

注意:@Inherited
annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

第3部分 java自带的Annotation


理解了上面的3个类的作用之后,我们接下来可以讲解Annotation实现类的语法定义了。

Java 内置的Annotation

从java5版本开始,自带了三种标准annontation类型,

  • Override,java.lang.Override 是一个marker
    annotation类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时加一个保障性的校验过程。
  • Deprecated,Deprecated也是一种marker
    annotation。,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的
    “延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为
    @Deprecated,但编译器仍然要报警。注意:@Deprecated这个annotation类型和javadoc中的
    @deprecated这个tag是有区别的:前者是java编译器识别的,而后者是被javadoc工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)。
  • SuppressWarnings,此注解能告诉Java编译器关闭对类、方法及成员变量的警告。有时编译时会提出一些警告,对于这些警告有的隐藏着Bug,有的是无法避免的,对于某些不想看到的警告信息,可以通过这个注解来屏蔽。SuppressWarning不是一个marker
    annotation。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,,同时编译器忽略掉无法识别的警告名。

下面我们来使用Java内置的Annotation 和 自定义的Annotation

public class AnnotationExample {
    @Override
    @MethodInfo(author = "xxx",version = "1.0",date = "2015/03/26",comment = "override toString()")
    public String toString() {
        return "AnnotationExample{}";
    }

    @Deprecated
    @MethodInfo(comment = "deprecated method", date = "2015/03/26")
    public static void oldMethod() {
        System.out.println("old method, don't use it.");
    }

    @SuppressWarnings({ "unchecked", "deprecation" })
    @MethodInfo(author = "Pankaj", comment = "Main method", date = "Nov 17 2012", version = "1.0")
    public static void genericsTest() {
        oldMethod();
    }
}

1 Annotation通用定义

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

说明:

上面的作用是定义一个Annotation,它的名字是MyAnnotation1。定义了MyAnnotation1之后,我们可以在代码中通过“@MyAnnotation1”来使用它。
其它的,@Documented, @Target, @Retention,
@interface都是来修饰MyAnnotation1的。下面分别说说它们的含义:

(01) @interface
     
 使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解就是一个Annotation。
      * **定义Annotation时,@interface是必须的。*
     
 注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口。

(02) @Documented 
     
 类和方法的Annotation在缺省情况下是不出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中。
     
 定义Annotation时,@Documented可有可无;若没有定义,则Annotation不会出现在javadoc中。

(03) @Target(ElementType.TYPE)
      前面我们说过,ElementType
是Annotation的类型属性。而@Target的作用,就是来指定Annotation的类型属性。
      @Target(ElementType.TYPE)
的意思就是指定该Annotation的类型是ElementType.TYPE。这就意味着,MyAnnotation1是来修饰“类、接口(包括注释类型)或枚举声明”的注解。
   
  定义Annotation时,@Target可有可无。若有@Target,则该Annotation只能用于它所指定的地方;若没有@Target,则该Annotation可以用于任何地方。

(04) @Retention(RetentionPolicy.RUNTIME)
      前面我们说过,RetentionPolicy
是Annotation的策略属性,而@Retention的作用,就是指定Annotation的策略属性。
      @Retention(RetentionPolicy.RUNTIME)
的意思就是指定该Annotation的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将该Annotation信息保留在.class文件中,并且能被虚拟机读取。
   
  定义Annotation时,@Retention可有可无。若没有@Retention,则默认是RetentionPolicy.CLASS。

 

使用反射来解析Annotation

注意我们的Annotation的Retention Policy
必须是RUNTIME,否则我们无法在运行时从他里面获得任何数据。

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

/**
 * Created by Administrator on 2015/3/26.
 */
public class AnnotationParsing {

    public static void main(String[] args) {
        for (Method method: AnnotationExample.class.getMethods()) {
            if (method.isAnnotationPresent(MethodInfo.class)) {
                for (Annotation annotation:method.getAnnotations()) {
                    System.out.println(annotation + " in method:"+ method);
                }

                MethodInfo methodInfo = method.getAnnotation(MethodInfo.class);

                if ("1.0".equals(methodInfo.version())) {
                    System.out.println("Method with revision no 1.0 = "
                            + method);
                }
            }
        }
    }
}

2 java自带的Annotation

通过上面的示例,我们能理解:@interface用来声明Annotation,@Documented用来表示该Annotation是否会出现在javadoc中,
@Target用来指定Annotation的类型,@Retention用来指定Annotation的策略。

理解这一点之后,我们就很容易理解java中自带的Annotation的实现类,即Annotation架构图的右半边。如下图:

澳门新葡亰3522平台游戏 3

java 常用的Annotation:

@Deprecated  -- @Deprecated 所标注内容,不再被建议使用。
@Override    -- @Override 只能标注方法,表示该方法覆盖父类中的方法。
@Documented  -- @Documented 所标注内容,可以出现在javadoc中。
@Inherited   -- @Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
@Retention   -- @Retention只能被用来标注“Annotation类型”,而且它被用来指定Annotation的RetentionPolicy属性。
@Target      -- @Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。
@SuppressWarnings -- @SuppressWarnings 所标注内容产生的警告,编译器会对这些警告保持静默。

 

由于“@Deprecated和@Override”类似,“@Documented, @Inherited, @Retention,
@Target”类似;下面,我们只对@Deprecated, @Inherited, @SuppressWarnings
这3个Annotation进行说明。

2.1 @Deprecated

@Deprecated 的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

说明

(01) @interface —
它的用来修饰Deprecated,意味着Deprecated实现了java.lang.annotation.Annotation接口;即Deprecated就是一个注解。
(02) @Documented — 它的作用是说明该注解能出现在javadoc中。
(03) @Retention(RetentionPolicy.RUNTIME) —
它的作用是指定Deprecated的策略是RetentionPolicy.RUNTIME。这就意味着,编译器会将Deprecated的信息保留在.class文件中,并且能被虚拟机读取。
(04) @Deprecated 所标注内容,不再被建议使用。
       例如,若某个方法被 @Deprecated
标注,则该方法不再被建议使用。如果有开发人员试图使用或重写被@Deprecated标示的方法,编译器会给相应的提示信息。示例如下:

澳门新葡亰3522平台游戏 4

源码如下(DeprecatedTest.java):

package com.skywang.annotation;

import java.util.Date;
import java.util.Calendar;

public class DeprecatedTest {
    // @Deprecated 修饰 getString1(),表示 它是建议不被使用的函数
    @Deprecated
    private static void getString1(){
        System.out.println("Deprecated Method");
    }

    private static void getString2(){
        System.out.println("Normal Method");
    }

    // Date是日期/时间类。java已经不建议使用该类了
    private static void testDate() {
        Date date = new Date(113, 8, 25);
        System.out.println(date.getYear());
    }
    // Calendar是日期/时间类。java建议使用Calendar取代Date表示“日期/时间”
    private static void testCalendar() {
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.get(Calendar.YEAR));
    }

    public static void main(String[] args) {
        getString1(); 
        getString2();
        testDate(); 
        testCalendar();
    }
}

说明

上面是eclipse中的截图,比较类中 “getString1() 和 getString2()” 以及
“testDate() 和 testCalendar()” 。

(01) getString1()
被@Deprecated标注,意味着建议不再使用getString1();所以getString1()的定义和调用时,都会一横线。这一横线是eclipse()对@Deprecated方法的处理。
       getString2() 没有被@Deprecated标注,它的显示正常。

(02) testDate()
调用了Date的相关方法,而java已经建议不再使用Date操作日期/时间。因此,在调用Date的API时,会产生警告信息,途中的warnings。
       testCalendar()
调用了Calendar的API来操作日期/时间,java建议用Calendar取代Date。因此,操作Calendar不回产生warning。