JAVA注解Annotation

JAVA注解Annotation

  • JAVA注解Annotation
    • 什么是注解
    • 为什么要引入注解
    • JDK内建Annotation
    • JDK元Annotation
    • Annotation示例
    • 自定义注解
    • 提取Annotation信息
    • Annotation处理器编写
    • 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异常。

时间: 2024-08-07 00:05:05

JAVA注解Annotation的相关文章

Java注解Annotation解析_java

概述        Java在1.5版本引入注解Annotation,又称Java标注,注解是一种语法元数据,可以被直接使用到源代码中,类/方法/变量/参数/包名等都可以被注解.和Javadoc标签不同,编译器在生成class文件时候能够保留注解代码,同时,可能为了在程序运行过程中(run-time)可以使用注解,Java虚拟机会把注解保留,这样就可以通过反射获取注解Annotation的相关信息. 内置注解 其实我们平时会经常遇见注解,例如@Override.@Deprecated等等,这些都

公共技术点之 Java 注解 Annotation

转自  :http://codekk.com/open-source-project-analysis/detail/Android/Trinea/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20Java%20%E6%B3%A8%E8%A7%A3%20Annotation 不少开源库都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义.解析,并对几个 Android 开源

Java注解Annotation详解

[本文转载于http://www.open-open.com/lib/view/open1423558996951.html] 从JDK5开始,Java增加了Annotation(注解),Annotation是代码里的特殊标记,这些标记可以在编译.类加载.运行时被读取,并执行相应的处理.通过使用Annotation,开发人员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息.代码分析工具.开发工具和部署工具可以通过这些补充信息进行验证.处理或者进行部署. Annotation提供了一种为

基于Java注解(Annotation)的自定义注解入门介绍_java

要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. -------------------------------------------------------------------------------- 元注解: 元注解的作用就是负责注解其他注解.Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明.Java5.0定义的元注解:

java教程之java注解annotation使用方法_java

1.概述 注解可以定义到方法上,类上,一个注解相当与一个类,就相当于实例了一个对象,加上了注解,就相当于加了一个标志. 常用的注解:@Override:表示重新父类的方法,这个也可以判断是否覆盖的父类方法,在方法前面加上此语句,如果提示的错误,那么你不是覆盖的父类的方法,要是提示的没有错误,那么就是覆盖的父类的方法.@SuppressWarnings("deprecation"):取消编译器的警告(例如你使用的方法过时了)@Deprecated:在方法的最上边也上此语句,表示此方法过时

基于Java 注解(Annotation)的基本概念详解_java

什么是注解(Annotation): Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法.Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据. Annotation(注解)是JDK5.0及以后版本引入的.它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查.从某些方面看,annotation就像修饰符一样被使用,并应用于包

Java 注解详解 (annotation)

什么是java注解 注解是java5的新特性.注解可以看做一种注释或者元数据(MetaData),可以把它插入到我们的java代码中,用来描述我们的java类,从而影响java类的行为. Java注解的目的 使用Java注解一般来说主要有三种目的 构建时指示: RetentionPolicy.SOURCE 编译期指示: RetentionPolicy.CLASS 运行时指示: RetentionPolicy.RUNTIME Java注解可以用在构建期.当构建我们的工程时,构建进程会编译源码.生成

java javax.annotation.Resource注解的详解_java

java 注解:java javax.annotation.Resource  当我们在xml里面为类配置注入对象时,会发现xml文件会越来越臃肿,维护起来很麻烦.这时候我们可以使用注解这种机制来为类配置注入对象.        Java为我们提供了 javax.annotation.Resource这个注解.        spring框架提供了org.springframework.beans.factory.annotation.Autowired.       一般情况下我们使用 jav

Java Annotation(Java 注解)的实现代码_java

如果你想知道java annotation是什么?你可以先看看:"http://www.infoq.com/articles/Annotation-Hammer" 下面是我做的一个demo: 项目结构: 运行效果: ==================================================== 代码部分: 注:很多人会考虑这个问题,"这样做的目的是什么?我们可以做一个配置文件(xml,properties等),不是比这个跟方便...或者说 直接把我们