案例介绍
案例一:普通注解用法
下面的代码定义了一个注解 @Test
,然后在 AnnotationTest
中获取到这个注解,然后打印出它 value()
方法的值。
/* by yours.tools - online tools website : yours.tools/zh/calcthickness.html */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
String value() default "";
}
@Test("test")
public class AnnotationTest {
public static void main(String[] args) {
Test test = AnnotationTest.class.getAnnotation(Test.class);
System.out.println(test.value());
}
}
执行上面的代码,结果输出如下:
案例二:组合注解用法
下面的代码中尝试从某个类上获取它的注解,然后再从这个注解中获取它上面的注解。组合注解在 Spring 中很常见,比如常用的 @RestController
,它实际上就是组合了 @Controller
和 @ResponseBody
这两个注解。
/* by yours.tools - online tools website : yours.tools/zh/calcthickness.html */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Test("composite")
public @interface TestComposite {
}
@TestComposite
public class AnnotationTest {
public static void main(String[] args) {
TestComposite testComposite = AnnotationTest.class.getAnnotation(TestComposite.class);
boolean existInProxyObj = testComposite.getClass().isAnnotationPresent(Test.class);
System.out.println("是否在动态代理对象的Class对象上存在:" + existInProxyObj);
boolean existInOriginal = testComposite.annotationType().isAnnotationPresent(Test.class);
System.out.println("是否在原始的Class对象上存在:" + existInOriginal);
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
执行上面的代码,结果输出如下:
可以看到执行结果在 testComposite.getClass()
返回的 Class 对象上不存在 @Test
这个注解,而是在 testComposite.annotationType()
返回的 Class 对象上才存在 @Test
这个注解。这个问题的原理是什么呢?
先说下结论:
- 使用
@interface
定义了一个注解之后编译得到的 class 文件会默认继承@Annotation
这个接口; - 在代码中获取注解时实际上是获取到的是 JDK 为它创建的一个动态代理对象。这个动态代理对象实现了注解定义的方法,当代码中调用注解的方法获取值时,实际上是调用的这个动态代理对象的方法获取到的值;
- 接口上修饰的注解无法在其实现类上获取到;
源码分析
先来看下 Annotation
接口,它里面定义了 annotationType()
方法,也就是上面组合注解调用的方法,所有注解在编译之后生成的 class 文件都会自动继承该接口。通过查看 @Test
注解编译之后的注解对应的 class 文件,可以看到它继承了 Annotation
接口。代码和执行结果如下:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
当调用 Class
类中 getAnnotation()
方法获取注解对象时,实际上是从 AnnotationData
的 annotations
属性中获取的,这个属性实际上就是一个 Map
对象,它的 key 是一个注解的 Class
对象,value 是 Annotation
类的一个对象实例,即注解的动态代理对象。代码如下:
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
Objects.requireNonNull(annotationClass);
return (A) annotationData().annotations.get(annotationClass);
}
private static class AnnotationData {
// 这里是一个Map结构,
final Map<Class<? extends Annotation>, Annotation> annotations;
final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
// Value of classRedefinedCount when we created this AnnotationData instance
final int redefinedCount;
AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
int redefinedCount) {
this.annotations = annotations;
this.declaredAnnotations = declaredAnnotations;
this.redefinedCount = redefinedCount;
}
}
而 AnnotationData
对象的创建则是在 annotationData()
方法中完成的,在这个方法里面实际上调用了 createAnnotationData()
方法来创建 AnnotationData
对象,它首先调用了 AnnotationParser
的 parseAnnotations()
方法生成 Map
对象,然后基于它创建了 AnnotationData
对象,实现对注解的动态代理对象的生成就是在 AnnotationParser
中实现的。
private AnnotationData annotationData() {
while (true) {
AnnotationData annotationData = this.annotationData;
int classRedefinedCount = this.classRedefinedCount;
if (annotationData != null &&
annotationData.redefinedCount == classRedefinedCount) {
return annotationData;
}
AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);
if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {
return newAnnotationData;
}
}
}
private AnnotationData createAnnotationData(int classRedefinedCount) {
// 处理当前类的注解
Map<Class<? extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
Class<?> superClass = getSuperclass();
Map<Class<? extends Annotation>, Annotation> annotations = null;
// 处理父类的注解
if (superClass != null) {
Map<Class<? extends Annotation>, Annotation> superAnnotations =
superClass.annotationData().annotations;
for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {
Class<? extends Annotation> annotationClass = e.getKey();
if (AnnotationType.getInstance(annotationClass).isInherited()) {
if (annotations == null) { // lazy construction
annotations = new LinkedHashMap<>((Math.max(
declaredAnnotations.size(),
Math.min(12, declaredAnnotations.size() + superAnnotations.size())
) * 4 + 2) / 3
);
}
annotations.put(annotationClass, e.getValue());
}
}
}
if (annotations == null) {
annotations = declaredAnnotations;
} else {
annotations.putAll(declaredAnnotations);
}
// 创建AnnotationData对象
return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);
}
在 AnnotationParser
中最终会调用到它的 parseAnnotation2()
方法,在该方法中从二进制流中解析到注解的类型以及它的定义成员和值的映射关系。然后在 annotationForMap()
方法中创建动态代理对象,这个动态代理的方法的调用都会调用到 AnnotationInvocationHandler
中。代码如下:
private static Annotation parseAnnotation2(ByteBuffer buf,
ConstantPool constPool,
Class<?> container,
boolean exceptionOnMissingAnnotationClass,
Class<? extends Annotation>[] selectAnnotationClasses) {
int typeIndex = buf.getShort() & 0xFFFF;
Class<? extends Annotation> annotationClass = null;
String sig = "[unknown]";
try {
try {
sig = constPool.getUTF8At(typeIndex);
// 这里解析到注解的实际类型
annotationClass = (Class<? extends Annotation>)parseSig(sig, container);
} catch (IllegalArgumentException ex) {
// support obsolete early jsr175 format class files
annotationClass = (Class<? extends Annotation>)constPool.getClassAt(typeIndex);
}
} catch (NoClassDefFoundError e) {
// 省略代码
}
// 这里解析到成员和值的映射关系
Map<String, Object> memberValues =
new LinkedHashMap<String, Object>(type.memberDefaults());
// 省略代码
return annotationForMap(annotationClass, memberValues);
}
public static Annotation annotationForMap(
final Class<? extends Annotation> type, final Map<String, Object> memberValues) {
return AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation) Proxy.newProxyInstance(
type.getClassLoader(), new Class<?>[] { type },
new AnnotationInvocationHandler(type, memberValues));
}});
}
AnnotationInvocationHandler
实现了 InvocationHandler
接口,而 InvocationHandler
则是实现动态代理的老朋友了。在 AnnotationInvocationHandler
的 invoke()
方法中根据当前调用的方法获取名称,然后根据名称从 Map 中获取中对应的值,然后返回,这就是在代码中调用注解的方法能够拿到对应的设置的值的原理,底层的实现就是一个 Map。
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
// 这里的type就是对应的注解类型
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
public Object invoke(Object proxy, Method method, Object[] args) {
// 这里获取到对应方法的名称
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// 根据方法的名称从Map中映射获取到对应的值返回
Object result = memberValues.get(member);
if (result == null)
throw new IncompleteAnnotationException(type, member);
if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();
if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);
return result;
}
}
在 Java 程序启动的时候设置参数 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
可以让 JDK 把给注解生成的动态代理类的 Class 文件保存到磁盘上。下面是 JDK 给 @TestComposite
注解生成的动态代理类,可以看到它内部有个 InvocationHandler
类型的实例变量,当调用 annotationType()
方法时,实际上代理到了 InvocationHandler
的 invoke()
方法中。代码如下:
public final class $Proxy1 extends Proxy implements TestComposite {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("TestComposite").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(((Throwable)var2).getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(((Throwable)var3).getMessage());
}
}
}
问题解答
基于以上分析来回答一下组合注解那里提出的问题:为什么 testComposite.getClass()
返回的 Class
对象上不存在 @Test
这个注解,而是在 testComposite.annotationType()
返回的 Class
对象上才存在 @Test
这个注解呢?
testComposite.getClass()
方法返回的 Class
对象实际上就是上面的 $Proxy1.class
,它实现了 TestComposite
接口,而 @Test
注解实际上是标记在 @TestComposite
注解上的(本质上是一个接口),根据 Java 的规则,接口上面的注解在其实现类上是获取不到的,因此testComposite.getClass()
返回的 Class
对象上不存在 @Test
这个注解;
testComposite.annotationType()
返回的 Class
对象实际上就是 TestComposite.class
这个 Class
对象,而 @Test
注解实际上是标记在 @TestComposite
注解上的(本质上是一个接口),所以它上面是存在 @Test
这个注解的。