JAVA注解Annotation

JAVA 注解 Annotation

什么是注解?

  用一个词就能够描写叙述注解。那就是元数据,即一种描写叙述数据的数据。所以,能够说注解就是源码的元数据。

比方。以下这段代码:

@Override
publicString toString() {
    return"This is String Representation of current object.";
}

  上面的代码中。我重写了 toString()方法并使用了 @Override 注解。可是,即使我不使用 @Override 注解标记代码。程序也能够正常执行。那么。该注解表示什么?这么写有什么优点吗?其实。@Override 告诉编译器这种方法是一个重写方法 ( 描写叙述方法的元数据),假设父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。

假设我不小心拼写错误。比如将 toString()写成了 toStrring(){double r},而且我也没有使用 @Override 注解。那程序依旧能编译执行。

但执行结果会和我期望的大不同样。如今我们了解了什么是注解,而且使用注解有助于阅读程序。


  Annotation 是一种应用于类、方法、參数、变量、构造器及包声明中的特殊修饰符。它是一种由 JSR-175 标准选择用来描写叙述元数据的一种工具。

为什么要引入注解?

  使用 Annotation 之前 (甚至在使用之后),XML 被广泛的应用于描写叙述元数据。不知何时開始一些应用开发者和架构师发现 XML 的维护越来越糟糕了。他们希望使用一些和代码紧耦合的东西,而不是像 XML 那样和代码是松耦合的(在某些情况下甚至是全然分离的) 代码描写叙述。

假设你在 Google 中搜索“XML vs. annotations”,会看到很多关于这个问题的辩论。最有趣的是 XML 配置其实就是为了分离代码和配置而引入的。上述两种观点可能会让你非常疑惑,两者观点似乎构成了一种循环。但各有利弊。以下我们通过一个样例来理解这两者的差别。
  假如你想为应用设置非常多的常量或參数,这样的情况下。XML 是一个非常好的选择,由于它不会同特定的代码相连。假设你想把某个方法声明为服务,那么使用 Annotation 会更好一些,由于这样的情况下须要注解和方法紧密耦合起来,开发者也必须认识到这点。
  还有一个非常重要的因素是 Annotation 定义了一种标准的描写叙述元数据的方式。

在这之前,开发者通常使用他们自己的方式定义元数据。

比如。使用标记 interfaces,凝视。transient 关键字等等。每一个程序猿依照自己的方式定义元数据。而不像 Annotation 这样的标准的方式。
  眼下。很多框架将 XML 和 Annotation 两种方式结合使用,平衡两者之间的利弊。

JDK 内建 Annotation

注解 说明
@Override 当我们想要复写父类中的方法时,我们须要使用该注解去告知编译器我们想要复写这种方法。这样一来当父类中的方法移除或者发生更改时编译器将提示错误信息。

@Deprecated 当我们希望编译器知道某一方法不建议使用时。我们应该使用这个注解。

Java 在 javadoc 中推荐使用该注解,我们应该提供为什么该方法不推荐使用以及替代的方法。

@SuppressWarnings 这个仅仅是告诉编译器忽略特定的警告信息,比如在泛型中使用原生数据类型。

它的保留策略是 SOURCE(译者注:在源文件里有效)而且被编译器丢弃。

@SafeVarargs 修饰”堆污染”警告
@FunctionalInterface Java8 特有的函数式接口

注意:
1. value 特权:假设使用注解时仅仅须要为 value 成员变量指定值, 则使用注解时能够直接在该注解的括号里指定 value 值, 而无需使用 name=value 的形式. 如 @SuppressWarnings(“unchecked”)
2. 请坚持使用 @Override 注解: 假设在每一个方法中使用 Override 注解来声明要覆盖父类声明, 编译器就能够替你防止大量的错误.

JDK 元 Annotation

  元 Annotation 用于修饰其它的 Annotation 定义.

元注解 释义
@Retention 指明了该 Annotation 被保留的时间长短。取值(RetentionPoicy)有:1. SOURCE: 在源文件里有效(即源文件保留);2. CLASS: 在 class 文件里有效(即 class 保留);3. RUNTIME: 在执行时有效(即执行时保留)
@Target 指明该类型的注解能够注解的程序元素的范围。

假设 Target 元注解没有出现,那么定义的注解能够应用于程序的不论什么元素。取值 (ElementType) 有:1. CONSTRUCTOR: 用于描写叙述构造器;2. FIELD: 用于描写叙述域; 3. LOCAL_VARIABLE: 用于描写叙述局部变量; 4. METHOD: 用于描写叙述方法; 5. PACKAGE: 用于描写叙述包; 6. PARAMETER: 用于描写叙述參数; 7. TYPE: 用于描写叙述类、接口(包括注解类型) 或 enum 声明

@Documented 指明拥有这个注解的元素能够被 javadoc 此类的工具文档化。

这样的类型应该用于注解那些影响客户使用带凝视的元素声明的类型。

假设一种声明使用 Documented 进行注解,这样的类型的注解被作为被标注的程序成员的公共 API。

@Inherited 指明该注解类型被自己主动继承。假设用户在当前类中查询这个元注解类型而且当前类的声明中不包括这个元注解类型。那么也将自己主动查询当前类的父类是否存在 Inherited 元注解,这个动作将被反复执行知道这个标注类型被找到,或者是查询到顶层的父类。

Annotation 演示样例

package annotation;

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;

@Inherited
@Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{

}
@Testable
class SupperClass
{

}

class SubClass extends SupperClass
{
    public SubClass()
    {
        for(Annotation annotation:SubClass.class.getAnnotations())
        {
            System.out.println(annotation);
        }

        for(Annotation annotation:SubClass.class.getDeclaredAnnotations())
        {
            System.out.println("getDeclaredAnnotations:"+annotation);
        }
    }
}
package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

public class Client
{
    @Test
    public void Client()
    {
        new SubClass();
    }
}

执行结果:@annotation.Testable()

自己定义注解

  使用 @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 类型里面的參数该怎么设定:
1. 仅仅能用 public 或默认 (default) 这两个訪问权修饰. 比如,String value(); 这里把方法设为 defaul 默认类型; 
2. 參数成员仅仅能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型和 String,Enum,Class,annotations 等数据类型, 以及这一些类型的数组. 比如,String value(); 这里的參数成员就为 String;  
3. 假设仅仅有一个參数成员, 最好把參数名称设为”value”, 后加小括号。

  依据 Annotation 是否包括成员变量,能够把 Annotation 分为两类:
1. 标记 Annotation: 没有成员变量的 Annotation; 这样的 Annotation 仅利用自身的存在与否来提供信息;
2. 元数据 Annotation: 包括成员变量的 Annotation; 他们能够接受(和提供)很多其它的元数据。

  定义新注解使用 @interface 关键字。其定义过程与定义接口非常相似 (见上面的 @Testable), 须要注意的是:Annotation 的成员变量在 Annotation 定义中是以无參的方法形式来声明的, 其方法名和返回值类型定义了该成员变量的名字和类型, 而且我们还能够使用 default 关键字为这个成员变量设定默认值。

例如以下所看到的。

package annotation;

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;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface Tag
{
    String name() default "zzh";
    String description() default "excellent!";
}

  注解元素必须有确定的值,要么在定义注解的默认值中指定。要么在使用注解时指定。非基本类型的注解元素的值不可为 null。

因此, 使用空字符串或 0 作为默认值是一种经常使用的做法。

这个约束使得处理器非常难表现一个元素的存在或缺失的状态,由于每一个注解的声明中。全部元素都存在,而且都具有对应的值。为了绕开这个约束。我们仅仅能定义一些特殊的值,比如空字符串或者负数,一次表示某个元素不存在,在定义注解时。这已经成为一个习惯使用方法。


  自己定义的 Annotation 继承了 Annotation 这个接口,因此自己定义注解中包括了 Annotation 接口中所以的方法;

package java.lang.annotation;

public interface Annotation { 
    boolean equals(Object obj); 
    int hashCode();
    String toString();
    Class<? extends Annotation> annotationType();
}

提取 Annotation 信息

  使用 Annotation 修饰了类 / 方法 / 成员变量等之后, 这些 Annotation 不会自己生效, 必须由这些注解的开发者提供对应的工具来提取并处理 Annotation 信息 (当然, 仅仅有当定义 Annotation 时使用了 @Retention(RetentionPolicy.RUNTIME) 修饰,JVM 才会在装载 class 文件时提取保存在 class 文件里的 Annotation, 该 Annotation 才会在执行时可见, 这样我们才干够解析).
  Java 使用 Annotation 接口来代表程序元素前面的注解, 用 AnnotatedElement 接口代表程序中能够接受注解的程序元素. 像 Class Constructor FieldMethod Package 这些类都实现了 AnnotatedElement 接口.
  比方 Class 的定义:public final class Class extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement。
  AnnotatedElement 接口的 API 例如以下:

修饰符与类型 方法与描写叙述
T ==getAnnotation==(类 annotationClass) Returns this element’s annotation for the specified type if such an annotation is present, else null.
Annotation[] ==getAnnotations()== Returns all annotations present on this element.
Annotation[] ==getDeclaredAnnotations()== Returns all annotations that are directly present on this element.
boolean ==isAnnotationPresent==(类 annotationClass) Returns true if an annotation for the specified type is present on this element, else false.
package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

public class Client
{
    @Test
    public void client() throws NoSuchMethodException, SecurityException
    {
        Annotation[] annotations = this.getClass().getMethod("client").getAnnotations();
        for(Annotation annotation : annotations)
        {
            System.out.println(annotation.annotationType().getName());
        }
    }
}

执行结果:org.junit.Test
  假设须要获取某个注解中的元数据。则须要强转成所须要的注解类型,然后通过注解对象的抽象方法来訪问这些数据。

package annotation;

import java.lang.annotation.Annotation;

import org.junit.Test;

@Tag(name="hiddenzzh")
public class Client
{
    @Test
    public void client() throws NoSuchMethodException, SecurityException
    {
        Annotation[] annotations = this.getClass().getAnnotations();
        for(Annotation annotation : annotations)
        {
            if(annotation instanceof Tag)
            {
                Tag tag = (Tag)annotation;
                System.out.println("name:"+tag.name());
                System.out.println("description:"+tag.description());
            }
        }
    }
}

执行结果:
name:hiddenzzh
description:excellent!

Annotation 处理器编写

  注解对代码的语意没有直接影响, 他们仅仅负责提供信息给相关的程序使用. 注解永远不会改变被注解代码的含义, 但能够通过工具对被注解的代码进行特殊处理.

package annotation;

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;

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Testable
{
}
package annotation;

import java.io.IOException;

public class TestCase
{
    @Testable
    public void test1()
    {
        System.out.println("test1");
    }

    public void test2() throws IOException {
        System.out.println("test2");
        throw new IOException("我 test2 出错啦...");
    }

    @Testable
    public void test3() {
        System.out.println("test3");
        throw new RuntimeException("我 test3 出错啦...");
    }

    public void test4() {
        System.out.println("test4");
    }

    @Testable
    public void test5() {
        System.out.println("test5");
    }

    @Testable
    public void test6() {
        System.out.println("test6");
    }
}
package annotation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestableProcessor
{
    public static void process(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException
    {
        int passed = 0;
        int failed = 0;
        Object obj = Class.forName(className).newInstance();
        for(Method method:Class.forName(className).getMethods())
        {
            if(method.isAnnotationPresent(Testable.class))
            {
                try
                {
                    method.invoke(obj, null);
                    ++passed;
                }
                catch(IllegalAccessException | InvocationTargetException e)
                {
                    System.out.println("method"+method.getName()+"execute error:<"+e.getCause()+">");
                    e.printStackTrace(System.out);
                    ++failed;
                }
            }

        }
        System.out.println("共执行"+(failed+passed)+"个方法。成功:"+passed+"个");
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException
    {
        process("annotation.TestCase");
    }
}

执行结果:

test1
test3
method test3 execute error:< java.lang.RuntimeException: 我test3出错啦... >
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at annotation.TestableProcessor.process(TestableProcessor.java:19)
    at annotation.TestableProcessor.main(TestableProcessor.java:36)
Caused by: java.lang.RuntimeException: 我test3出错啦...
    at annotation.TestCase.test3(TestCase.java:21)
    ... 6 more
test5
test6
共执行 4个方法。成功:3

  注意到在 TestCase 中仅仅有 test1,test3,test5 以及 test6 标注了 @Testable 的注解,通过注解处理器 TestableProcessor 进行处理,仅仅执行了这个四个标注注解的方法,这个就是通过注解来实现 junit 功能的一个雏形。

Annotation 处理器处理异常

package annotation;

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.METHOD)
public @interface ExceptionTest
{
    Class<? extends Exception> value();
}
package annotation;

public class Sample
{
    @ExceptionTest(ArithmeticException.class)
    public static void m1()
    {
        int i=0;
        i=i/i;
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m2()
    {
        int [] a = new int[0];
        int i=a[1];
    }

    @ExceptionTest(ArithmeticException.class)
    public static void m3(){}
}
package annotation;

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

public class ExceptionProcess
{
    public static void main(String[] args) throws ClassNotFoundException
    {
        int tests = 0;
        int passed = 0;
        Class testClass = Class.forName("annotation.Sample");
        for(Method m:testClass.getDeclaredMethods())
        {
            if(m.isAnnotationPresent(ExceptionTest.class))
            {
                tests++;
                try{
                    m.invoke(null);
                    System.out.printf("Test %s failed: no exception%n",m);
                }
                catch(InvocationTargetException wrappedEx)
                {
                    Throwable exc = wrappedEx.getCause();
                    Class<? extends Exception> excType = m.getAnnotation(ExceptionTest.class).value();
                    if(excType.isInstance(exc))
                    {
                        passed++;
                    }
                    else
                    {
                        System.out.printf("Test %s failed: expected %s, got %s%n",m,excType.getName(),exc);
                    }
                }
                catch(Exception exc)
                {
                    System.out.println("INVALID @Test:"+m);
                }
            }
        }
    }
}

执行结果:

Test public static void annotation.Sample.m2() failed: expected java.lang.ArithmeticException, got java.lang.ArrayIndexOutOfBoundsException: 1
Test public static void annotation.Sample.m3() failed: no exception

  这三段程序相似于上面用来处理 Testable 注解的代码,但有一处不同:这段代码提取了注解參数的值,并用它检验改測试抛出的异常是否为正确的类型。没有显示的转换,因此没有出现 ClassCastException 的危急。编译过的測试程序确保它的注解參数表示的是有效的异常类型,须要提醒一点:有可能注解參数在编译时是有效的,可是表示特定异常类型的类文件在执行时却不再存在。

在这样的希望非常少出现的情况下。測试执行类抛出 TypeNotPresentException 异常。