什么是注解?
用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据。比如,下面这段代码:
@Override
publicString toString() {
return"This is String Representation of current object.";
}
上面的代码中,我重写了toString()方法并使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能够正常执行。那么,该注解表示什么?这么写有什么好处吗?事实上,@Override告诉编译器这个方法是一个重写方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。如果我不小心拼写错误,例如将toString()写成了toStrring(){double r},而且我也没有使用@Override注解,那程序依然能编译运行。但运行结果会和我期望的大不相同。现在我们了解了什么是注解,并且使用注解有助于阅读程序。
Annotation是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的一种工具。当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事
为什么要引入注解?
使用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
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
指明了该Annotation被保留的时间长短。取值(RetentionPoicy)有:1. SOURCE:注解信息仅仅保留在目标类代码的源代码文件中,对应的字节码文件将不再保留;2. CLASS:注解信息将进入目标类代码的字节码文件中,但是类加载器加载字节码文件时不会将注解加载到JVM中,也就是说运行期不能获取这些注解信息;3. RUNTIME:注解信息在目标类加载到JVM中以后仍然保留,在运行期可以通过反射机制读取类中的注解信息。
@Target
public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, /** Field declaration (includes enum constants) */ FIELD, /** Method declaration */ METHOD, /** Formal parameter declaration */ PARAMETER, /** Constructor declaration */ CONSTRUCTOR, /** Local variable declaration */ LOCAL_VARIABLE, /** Annotation type declaration */ ANNOTATION_TYPE, /** Package declaration */ PACKAGE, /** * Type parameter declaration * * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * * @since 1.8 */ TYPE_USE }
指明该类型的注解的应用目标。如果Target元注解没有出现,那么定义的注解可以应用于程序的任何元素。取值(ElementType)有:1. CONSTRUCTOR:用于描述构造器;2. FIELD:用于描述域; 3. LOCAL_VARIABLE:用于描述局部变量; 4. METHOD:用于描述方法; 5. PACKAGE:用于描述包; 6. PARAMETER:用于描述参数; 7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
@Documented
指明拥有这个注解的元素可以被javadoc此类的工具文档化。这种类型应该用于注解那些影响客户使用带注释的元素声明的类型。如果一种声明使用Documented进行注解,这种类型的注解被作为被标注的程序成员的公共API。
@Inherited
指明该注解类型被自动继承。如果用户在当前类中查询这个元注解类型并且当前类的声明中不包含这个元注解类型,那么也将自动查询当前类的父类是否存在Inherited元注解,这个动作将被重复执行知道这个标注类型被找到,或者是查询到顶层的父类。
自定义注解
使用@interface自定义注解时,自动继承了Java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {
成员
}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface NeedTest{ boolean value() default false; }
一个注解可以拥有多个成员,成员声明和接口方法声明类似。,这里仅定义了一个成员。
a) 成员以无入参无抛出异常的方式声明,如boolean value(String str)、boolean value() throws Exception等方式是非法的;
b) 可以通过default为成员指定一个默认值,如String level() default “LOW_LEVEL”、int high() default 2是合法的,当然也可以不指定默认值;
c) 成员类型是受限的,合法的类型包括原始类型及其封装类、String、Class、enums、注解类型,以及上述类型的数组类型。如ForumService value()、List foo()是非法的。
d) 如果注解只有一个成员,则成员名必须取名为value(),在使用时可以忽略成员名和赋值号(=),如@Description(“使用注解的实例”)。注解类拥有多个成员时,如果仅对value成员进行赋值则也可不使用赋值号,如果同时对多个成员进行赋值,则必须使用赋值号,如@DeclareParents (value = “NaiveWaiter”, defaultImpl = SmartSeller.class)。
根据Annotation是否包含成员变量,可以把Annotation分为两类:
1.标记Annotation:没有成员变量的Annotation;这种Annotation仅利用自身的存在与否来提供信息;
2.元数据Annotation:包含成员变量的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
@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 Exception {
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 (Exception 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 Exception {
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功能的一个雏形。
参考:
http://blog.csdn.net/u013256816/article/details/50598522