Java中的注解

Java中的注解

什么是注解

注解也叫元数据,例如我们常见的@Override@Deprecated,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。

它主要的作用有以下四方面:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。

注解的分类

注解一般可以分为三类:

  1. 元注解:元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
  2. JDK自带的标准注解:包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
  3. 自定义注解:根据自己的需求定义注解,并可用元注解对自定义注解进行注解

注解的声明

可以按照如下形式声明一个自定义的注解

1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
String value() default "";

}

使用@interface语法,声明一个CustomAnnotation,然后使用元注解@Target(ElementType.FIELD)限定该注解只能用于属性值上,注解@Rentention(RetentionPolicy.RUNNTIME)限定,该注解的生存期是运行时。

@Target还有其他几种取值,分别是:

  • TYPE:标明该注解可以用于类、接口(包括注解类型)或enum声明
  • FIELD:标明该注解可以用于字段(域)声明,包括enum示例
  • METHOD:标明该注解可以用于方法声明
  • PARAMETER:标明该注解可以用于参数声明
  • CONSTRUCTOR:标明该注解可以用于构造函数声明
  • LOCAL_VARIABLE:标明该注解可以用于局部变量声明
  • ANNOTATION_TYPE:标明该注解可以用于局部变量声明
  • PACKAGE:标明该注解可以用于包声明
  • TYPE_PARAMETER:标明该注解可以用于类型参数声明
  • TYPE_USE:标明该注解可以用于类型使用声明

注意:当注解未指定Target值时,该注解可以用于任何元素值上,多个值可以使用花括号包围,以逗号分隔。

@Target(value={CONSTRUCTOR,FIELD,...,METHOD})

@Retetion用来约束注解的声明周期,可以有3个取值:source(源码级别)、class(类文件级别)和runtime(运行时级别)。如果注解未定义Rentetion的时候,默认级别是CLASS。

  • SOURCE:注解将会被编译器丢弃。此类型的注解信息只会保留在源码里,源码经过编译后,注解的信息会被丢弃,不会保留在编译好的class文件里。
  • CLASS:注解在class文件中可用,但会在JVM丢弃。此类型的注解信息会保留在源码里和class文件里,在执行的时候不会加载到虚拟机中。
  • RUNTIME:注解细腻系将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)。

注解的原理

下面我们用这两段程序看看注解的实现原理

CustomAnnotation注解

1
2
3
4
5
6
@Target(value = {ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
String value() default "CustomAnnotation";
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AnnotationTest {

@CustomAnnotation(value = "test")
private static String value = "";

@CustomAnnotation
private static String defaultValue;
@CustomAnnotation
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Class cls = AnnotationTest.class;
Method method = cls.getMethod("main", String[].class);
CustomAnnotation annotation = method.getAnnotation(CustomAnnotation.class);
}

@Override
public String toString() {
return "";
}
}

语句System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");的目的是为了将运行过程中的中间类保存到磁盘上,供分析。

首先,在main方法处打个断点,看看

可以发现annotation其实是一个代理对象。

接着,在看看运行main方法之后生成的中间文件

可以看到它生成了两个中间文件,$Proxy0$Proxy1

因为CustomAnnotation是$Proxy1对象,我们反编译一下看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package com.sun.proxy;

import CustomAnnotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy1 extends Proxy implements CustomAnnotation {
private static Method m1;

private static Method m2;

private static Method m4;

private static Method m0;

private static Method m3;

public $Proxy1(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}

public final boolean equals(Object paramObject) {
try {
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final Class annotationType() {
try {
return (Class)this.h.invoke(this, m4, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode() {
try {
return ((Integer)this.h.invoke(this, m0, null)).intValue();
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final String value() {
try {
return (String)this.h.invoke(this, m3, null);
} catch (Error|RuntimeException error) {
throw null;
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m4 = Class.forName("CustomAnnotation").getMethod("annotationType", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m3 = Class.forName("CustomAnnotation").getMethod("value", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}

从代码中得知,$Proxy1类实现了CustomAnnotation接口,说明它就是CustomAnnotation的动态代理类。

然后我们再反编译下CustomAnnotation的字节码

反编译CustomAnnotation

可以看到第二个红框,有一个ACC_ANNOTATION的标记,标明这是一个注解。而第一个红框,表示接口CustomAnnotation扩展了接口Annotation。所以,注解的本质就是一个继承了接口Annotation的接口

而Annotation接口声明了以下的方法:

Annotation接口的方法

这些方法,正是动态代理类$Proxy1实现的方法。

0%