快速理解JAVA注解

前两天在import new上看见了一篇关于java注解的文章,分了上下两篇,基本是把《Java-Annotations-Tutorial》翻译了一遍,内容较多,同时觉得有点乱,所以打算以我的角度来尝试讲一下(讲得不好请拍砖),究竟什么是annotations,怎么用。

接下来我将以声明一个用以做属性空值校验的注解为例,若检测到被注解属性为null,则抛出空指针异常。

首先,声明注解:

package annotation.declaration;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)

public @interface NullValueValidate {
String paramName();

}

这个跟我们定义接口时的做法非常像,但是区别在于@interface这里,通常的接口定义是不会有@这个符号的,这个用法是专门用以声明一个注解的,这个叫“注解接口”。

同时我们可以看到,在这个注解之上,还有三个注解,@Documented、@Target、@Retention,这三个隶属于内建注解,其作用是:

@documented 被此注解的,会在使用java docs的时候被自动加入创建docs,不注解这个注解的话,默认是不会被创建docs的。

@target 被注解的目标类型,比如此例中,是field,当然我们也可以定位被我们注解的目标是method等。

(附:

  • TYPE – 可用于类、接口、枚举,甚至是注解上
  • FIELD –表示可以使用到域或属性上
  • METHOD –可以使用到方法级别的注解上
  • PARAMETER – 可以使用到方法的参数上
  • CONSTRUCTOR –表示可以使用到构造器上
  • LOCAL_VARIABLE – 表示可以使用到局部变量上(那些在method或者局部作用域中声明的变量).
  • ANNOTATION_TYPE –表示该注解可以应用到其他注解上
  • PACKAGE –可以使用到包声明上

@retention 提供3种不同的注解保留时长:

  • SOURCE:表明这个注解会被编译器忽略,并只会保留在源代码中。
  • CLASS:表明这个注解会通过编译驻留在CLASS文件,但会被JVM在运行时忽略,正因为如此,其在运行时不可见。
  • RUNTIME:表示这个注解会被JVM获取,并可能在运行时通过反射被读取。

此外,注解中声明的方法和注解的参数相对应:

 paramName() – 是我们这个例子中唯一声明的方法,它保存被注解field的参数为paramName的值,用于传递给接下来我们要说的注解处理器(annotation processor)进行相应的处理调用paramName()将获取到这个注解对象的元素信息(比如:@NullValueValidate (paramName=“ching”),则调用这个方法将获取到字符串”ching”)。

下面我们通过阅读定义空指针注解处理器的代码来理解:

public class NullProcessor {
/**

     * Method to process all the annotations

     * @param obj    要被处理的包含注解的对象

     */
    public static void processAnnotations(Object obj) {
try {
Class cl = obj.getClass();

// 检查所有属性

for(Field f : cl.getDeclaredFields()) {

 

try {
// 处理一个属性上的所有注解
for(Annotation a : f.getAnnotations()) {

// 检查是否是NullValueValidate

if(a.annotationType() == NullValueValidate.class) {

NullValueValidate nullVal = (NullValueValidate) a;

//访问注解的method:paramName,将获取到paramName的值

System.out.println(“Processing the field : ” + nullVal.paramName());

//如果这个属性是private的,需要先设置它能够被访问。

f.setAccessible(true);

//检查是否为空,为空则抛出空指针异常

if(f.get(obj) == null) {
throw new NullPointerException(“The value of the field “+f.toString()+” can’t be NULL.”);
} else
System.out.println(“Value of the Object : “+f.get(obj));
}
}
} catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
} catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}

}

在这个代码中已经加了相关注释,基本看注释就可以读懂整个代码的意思:这里通过一个静态方法processAnnotations()接收到含有注解的对象,通过反射的api,一一检查这个对象含有的属性,检查它们的注解类型,并在检测到注解类型是@NullValueValidate时,采取相应的空值检测,抛出空指针异常。

最后,我们想要测试运行一下:

public class AnnotationExample {
@NullValueValidate(paramName = “chingzhu”)
private String testVar1;
@NullValueValidate(paramName = “icekredit”)
private String testVar2;

public AnnotationExample() {
testVar2 = “不是空”;
NullProcessor.processAnnotations(this);
}

public static void main(String args[]) {
AnnotationExample ae = new AnnotationExample();
}

运行结果:

java.lang.NullPointerException: The value of the field private java.lang.String com.chingzhu.example.AnnotationExample.testVar1 can’t be NULL.

Processing the field : chingzhu

at com.chingzhu.example.NullProcessor.processAnnotations(NullProcessor.java:52)

The value of the field private java.lang.String com.chingzhu.example.AnnotationExample.testVar1 can’t be NULL.

at com.chingzhu.example.AnnotationExample.<init>(AnnotationExample.java:19)

Processing the field : icekredit

at com.chingzhu.example.AnnotationExample.main(AnnotationExample.java:23)

Value of the Object : 不是空

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:497)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

至此,我想应该说清楚了java注解和其是如何工作的,从原理上讲,注解的作用依靠于在其中声明的方法和注解处理器,在注解处理器中通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。

实际上,读者应该发现了,这里我们是通过一个静态方法,自己调用的注解处理器。而像上面我们提到的@target,@rentation等对应的注解处理器都是内建在jvm中的,下一次我将详细介绍一下APT(Annotation processing-tool),教你如何注册你自定义的注解处理器。