大家都知道注解是实现了java.lang.annotation.Annotation接口,眼见为实,耳听为虚,有时候眼见也不一定是真实的。
/** * The common interface extended by all annotation types. Note that an * interface that manually extends this one does <i>not</i> define * an annotation type. Also note that this interface does not itself * define an annotation type. * * More information about annotation types can be found in section 9.6 of * <cite>The Java™ Language Specification</cite>. * * The {@link java.lang.reflect.AnnotatedElement} interface discusses * compatibility concerns when evolving an annotation type from being * non-repeatable to being repeatable. * * @author Josh Bloch * @since 1.5 */
元注解 一般用于指定某个注解生命周期以及作用目标等信息。正如源码的注释一样,如果自定义的注解没有添加元注解就和平常的注释没有多大的区别,有了元注解就会让编译器将信息编译进字节码文件。
@Target
用于指明被修饰的注解最终可以作用的目标
ElementType
是一个枚举类型
ElementType.TYPE:类,接口(包括注释类型)或枚举声明 ElementType.FIELD:字段声明(包括枚举常量) ElementType.METHOD:方法声明 ElementType.PARAMETER:正式参数声明 ElementType.CONSTRUCTOR:构造器声明 ElementType.LOCAL_VARIABLE:本地局部变量声明 ElementType.ANNOTATION_TYPE:注解声明 ElementType.PACKAGE:包声明 ElementType.TYPE_PARAMETER:类型参数声明 jdk1.8新增 ElementType.TYPE_USE:使用一种类型 jdk1.8新增
@Retention
用于指明当前注解的生命周期
RetentionPolicy
是一个枚举类型
RetentionPolicy.SOURCE:编译器将丢弃注释。 RetentionPolicy.CLASS:注释将由编译器记录在类文件中,但在运行时不需要由VM保留。 RetentionPolicy.RUNTIME:注释将由编译器记录在类文件中并且在运行时由VM保留,因此可以反射性地读取它们。
@Documented
表示具有类型的注释将由javadoc记录和默认的类似工具。 这种类型应该用来注释注解影响注解使用的类型的声明客户的元素。 如果使用注解类型声明记录,其注解成为公共API的一部分注释元素。
@Inherited
表示自动继承注解类型。 如果注解类型上存在继承的元注解声明,用户查询类的注解类型声明,类声明没有此类型的注解,然后将自动查询该类的超类注解类型。 将重复此过程,直到为此注解找到类型,或类层次结构的顶部(对象)到达了。 如果没有超类具有此类型的注解,那么查询将指示有问题的类没有这样的注解。请注意,如果带注解,则此元注解类型无效 type
用于注解除类之外的任何内容。 另请注意这个元注解只会导致注解被继承来自超类; 已实现的接口上的注解没有效果。
package com.github.dqqzj.springboot.annotation; import org.springframework.core.annotation.AliasFor; import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author qinzhongjian * @date created in 2019-07-28 07:54 * @description: TODO * @since JDK 1.8.0_212-b10 */ @Target(value = {ElementType.TYPE}) @Retention(value = RetentionPolicy.RUNTIME) @Component public @interface Hello { @AliasFor( annotation = Component.class ) String value() default "hi" ; }
如上图所示注解其实也是使用了代理,而且是JDK代理的。
既然是运行时生成的代理类,我们就可以在启动类上添加 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true")
或者
我们来分析一下生成的代理类
package com.sun.proxy; import com.github.dqqzj.springboot.annotation.Hello; 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 Hello { private static Method m1; private static Method m2; private static Method m4; private static Method m0; private static Method m3; 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, m4, (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); } } public final String value() throws { try { return (String)super.h.invoke(this, m3, (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"); m4 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("annotationType"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("com.github.dqqzj.springboot.annotation.Hello").getMethod("value"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
这里的 InvocationHandler
实际上是我们的 AnnotationInvocationHandler
,这里有一个 memberValues
,它是一个 Map
键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。接下来我调试代码给大家分享一下奥秘。
Hello hello = TestAnnotation.class.getAnnotation(Hello.class)
这个部分的调试代码我会忽略直接调试
AnnotationInvocationHandler
的相关方法。
Annotation
接口的
熟悉jdk规范的就会发现最底部的 s#7RuntimeVisibleAnnotations
这个是运行时可访问的注解信息,可供我们反射获取。
虚拟机规范定义了一系列和注解相关的属性表,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:
RuntimeVisibleAnnotations:运行时可见的注解 RuntimeInVisibleAnnotations:运行时不可见的注解 RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解 RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解 AnnotationDefault:注解类元素的默认值`
说明: 明明只有一个 @Hello
注解为什么左侧会出现2个代理类的原因就在这个地方,会多出一个代理类
public final class $Proxy0 extends Proxy implements Retention { //省略无关代码....... }
@Hello(value = "hi") RUNTIME
我们已经知道了注解的值是存放在 Map<String, Object> memberValues
中的,那么我们就可以使用反射获取并重新赋值。