同时发布于 知乎 Java 注解
1.注解的由来
在引入注解之前,在不同类型的应用程序使用XML作为标准的代码配置机制,程序员们描述其代码的形式尚未标准化,每个人的做法各异:transient关键字、注释、接口等,代码和XML的解耦以及未来对这种解耦应用的维护并不低廉,这显然不是一种优雅的方式,随之而来的JDK5.0引入一种崭新的记录元数据的形式——注解被引入到Java中。
它的作用是修饰编程元素。什么是编程元素呢?例如:包、类、构造方法、方法、成员变量等。
2.什么是注解
DK5.0中的类型:1、类(class)2、接口(interface)3、枚举(enum)4、注解(Annotation)
因此,注解与其他3种类型一样,都可以定义、使用,以及包含有自己的属性、方法
注解分类
(1)标记注释:注解的内部没有属性,称作标记注解
使用方法:@注解名
使用例子:@MarkAnnotation
(2)单值注解:注解的内部只有一个属性,称作单值注解
使用方法:@注解名(属性名=属性值)
使用例子:@SingleAnnotation(value="abc") //也可以写成@SingleAnnotation("abc")
*(属性名=属性值)可以简化为(属性值),但是需要满足以下两个条件:
1、该注解必须为单值注解
2、该注解的属性名必须为value
(3)多值注解:注解的内部有多个属性,称作多值注解
使用方法:@注解名(属性名1=属性值1, 属性名2=属性值2……)
使用例子:@MultipliedAnnotation(value1 = "abc", value2 = 30……)
元注解
Java提供了一下几个元注解(下边会有介绍)
Target、Retention、Documented和Inherited
元注解的作用:
可以用于注解类(annotate Classes)
可以用于注解接口(annotate Interfaces)
可以用于注解枚举类型(annotate Enums)
因此注解同样也可以用于注解注解(annotate Annotations)
3.注解语法
3.1注解声明介绍
我们通过一个简单的例子了解下:
<pre>
@Annotation
@Annotation1(info = "I am Annotation")
public AnnotationMethod(){
//代码块
}
</pre>
从代码一步步看:
01.使用“@”作为前缀声明一盒注解,向编译器说明,该元素(Annotation)是注解
02.注解后面(),标注它的属性,采用键值对的形式,如果注解只有一个元素(或者只需要指定一个元素的值,其它则使用默认值),也可以表示成: @Annotation("I am Annotation");如果没有元素需要被指定,则不需要括号;
03.注解用途:可以标注在Java程序的每一个元素上使用:类,域,方法,包,变量等
3.2 预定义的注解
3.2.1 @Override
目的在于标识某一个方法是否覆盖了它的父类的方法
3.2.2 @Deprecated
属于标记注解.所谓标记注解,就是在源程序中加入这个标记后,并不影响程序的编译,但有时编译器会显示一些警告信息。
用于标明已经过时的方法或类
3.2.3 @SuppressWarnnings
用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告.
<pre> <code>
deprecation:使用了不赞成使用的类或方法时的警告;
unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
path:在类路径、源文件路径等中有不存在的路径时的警告;
serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
finally:任何 finally 子句不能正常完成时的警告;
all:关于以上所有情况的警告。
</code></pre>
4.定义注解
4.1 注解的定义
<pre> <code>
public @interface CustomAnnotationClass
</code></pre>
其中** @interface**说明这是一个自定义注解的定义.
4.2 定制化
定制化时,有很多其它属性可以用在自定义注解上,但是 ==目标(Target==)和 ==保留策略(Retention Policy==)是最重要的两个。
4.2.1 @Target
用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型
<pre>
<code>
public enum ElementType {
/*标明该注解可以用于类、接口(包括注解类型)或enum声明/
TYPE,
/** 标明该注解可以用于字段(域)声明,包括enum实例 */
FIELD,
/** 标明该注解可以用于方法声明 */
METHOD,
/** 标明该注解可以用于参数声明 */
PARAMETER,
/** 标明注解可以用于构造函数声明 */
CONSTRUCTOR,
/** 标明注解可以用于局部变量声明 */
LOCAL_VARIABLE,
/** 标明注解可以用于注解声明(应用于另一个注解上)*/
ANNOTATION_TYPE,
/** 标明注解可以用于包声明 */
PACKAGE,
/**
* 标明注解可以用于类型参数声明(1.8新加入)
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 类型使用声明(1.8新加入)
* @since 1.8
*/
TYPE_USE
}
</code>
</pre>
- 当注解未指定Target值时,则此注解可以用于任何元素之上
- 设定一个值:@Target(ElementType.METHOD)
- 设定多个值时使用{}包含并用逗号隔开
<code>@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})</code>
4.2.2 @Retention
用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)
SOURCE:注解将被编译器丢弃(该类型的注解信息只会保留在源码里,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件里)
CLASS:注解在class文件中可用,但会被VM丢弃(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中),请注意,当注解未定义Retention值时,默认值是CLASS,如Java内置注解,@Override、@Deprecated、@SuppressWarnning等
RUNTIME:注解信息将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息),如SpringMvc中的@Controller、@Autowired、@RequestMapping等。
4.2.3 注解中的细节
<pre>
<code>
@Target(ElementType.TYPE) // 注解在类上
@Retention(RetentionPolicy.RUNTIME) //保留在运行时
@interface CustmAnnotationClass {
/**
* Java 元注解
* 1. 目标
* 2. 保留策略
*/
String info() default "Annotation";
}
@CustmAnnotationClass(info="OOP")
public class Test {
}
</code>
</pre>
- 注解支持以下类型:
- 所有基本类型
(int,float,boolean,byte,double,char,long,short) - String
- Class
- enum
- Annomation
- 上述类型的数组形式
- 注解可以作为元素的类型,也就是嵌套注解
- 注解默认值限制
元素必须要么具有默认值,要么在使用注解时提供元素的值。
对于非基本类型的元素,无论是在源代码中声明,还是在注解接口中定义默认值,都不能以null作为值.当然我们可以定义一些特殊的值,例如空字符串或负数,表示某个元素不存在
4.2.4 注解中的继承
- 注解不能使用关键字extends来继承某个@interface,但注解在编译后,编译器会自动继承java.lang.annotation.Annotation接口
- @Inherited
使用了保留注解@Inherited,但这并不是真的继承,只是通过使用@Inherited,可以让子类Class对象使用getAnnotations()获取父类被@Inherited修饰的注解,换句话说:这个类将自动地把这个注解传递到所有子类中而不用在子类中声明,通常,一个类继承了父类,并不继承父类的注解。这完全和使用注解的目的一致的:提供关于被注解的代码的信息而不修改它们的行为。
所以:@Inheriated注解仅在存在继承关系的类上产生效果,在接口和实现类上并不工作。
在默认的情况下,父类的注解并不会被子类继承。如果要继承,就必须加上Inherited注解。
4.2.5 其他注解属性介绍
- @Documented 被修饰的注解会生成到javadoc中
5.注解的获取(反射)
在使用反射之前必须使用import java.lang.reflect.* 来导入和反射相关的类。
如果要得到某一个类或接口的注解信息,可以使用如下代码:
<pre>
<code>
Annotation annotation = TestAnnotation.class.getAnnotation(MyAnnotation.class);
</code>
</pre>
- 如果要得到全部的注解信息可使用如下语句:
<pre>
<code>
Annotation[] annotations = TestAnnotation.class.getAnnotations();
或
Annotation[] annotations = TestAnnotation.class.getDeclaredAnnotations();
</code>
</pre>
getDeclaredAnnotations与getAnnotations类似,但它们不同的是getDeclaredAnnotations得到的是当前成员所有的注解,不包括继承的。而getAnnotations得到的是包括继承的所有注解。
- 如果要得到其它成员的注解,可先得到这个成员,然后再得到相应的注解。如得到myMethod的注解。
<pre>
<code>
Method method = TestAnnotation.class.getMethod("myMethod", null);
Annotation annotation = method.getAnnotation(MyAnnotation.class);
</code>
</pre>
注:要想使用反射得到注解信息,这个注解必须使用
@Retention(RetentionPolicy.RUNTIME)进行注解。
- 判断指定类型的注解是否存在于此元素
<pre>
<code>
isAnnotationPresent(Class<? extends Annotation> annotationClass)
</code>
</pre>
6. Java8 变化
- @Repeatable
表示在同一个位置重复相同的注解
<code>@Repeatable(MyAnnotation.class)</code> - 新增ElementType
TYPE_PARAMETER 和 TYPE_USE ,在Java8前注解只能标注在一个声明(如字段、类、方法)上,Java8后,新增的TYPE_PARAMETER可以用于标注类型参数,而TYPE_USE则可以用于标注任意类型(不包括class)。
7.自动测试机的写法
7.1自动测试机的原理:
使用Annotation来Annotate元素的实质是:每一个ElementType内部的元素都有两个方法,分别为
(注:为方便理解,以下使用的TestCase为某个特定的自定义注释)
(1)isAnnotationPresent(TestCase.class) //判断该元素是否被TestCase所注释
(2)getAnnotation(TestCase.class) //获得TestCase的类对象
7.2自动测试机的工作过程是:
(1)首先通过反射,获得被测类o中的每一个方法
(2)对每一个方法通过使用isAnnotationPresent(TestCase.class)判断其是否被TestCase所注释(注意是.class!)
(3)如果某方法method被TestCase所注释,则通过method的getAnnotation(TestCase.class)获得TestCase的类对象tc
(4)通过tc的value()方法,获得该类对象的属性value
(注:此处使用的value()方法,正是在TestCase中定义的value属性,再次理解在注释中定义的value既是属性,也是方法)
(5)调用method方法的invoke(o,value),用value对method进行测试
参考福利
官方Java注解地址:http://docs.oracle.com/javase/tutorial/java/annotations/
维基百科中关于Java注解的解释:http://en.wikipedia.org/wiki/Java_annotation
Java规范请求250:http://en.wikipedia.org/wiki/JSR_250
Oracle 注解白皮书:http://www.oracle.com/technetwork/articles/hunter-meta-096020.html
注解API:http://docs.oracle.com/javase/7/docs/api/java/lang/annotation/package-summary.html