概述

注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:@Controller@Service@RequestMapping等等,它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

注解的分类

根据注解的使用场景,主要分为三类,元注解、内置注解和自定义注解。

元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。简而言之,元注解是用来修饰注解的。

@Retention

指定注解信息保留到哪个阶段,分别为源代码阶段、编译Class阶段、运行阶段。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. /**
  6. * Returns the retention policy.
  7. * @return the retention policy
  8. */
  9. RetentionPolicy value();
  10. }
  • SOURCE: 保留在源代码java中,被编译器丢弃,也就是说在class文件中不包含注解信息,通常用来标记源码,引起大家的注意,比如自定义一个注解例如@ThreadSafe,用来标识一个类时线程安全的。
  • CLASS:编译后的class文件中包含注解信息,但是会被jvm丢弃
  • RUNTIME: 注解信息在运行期(JVM)保留(.class也有),可以通过反射机制读取注解的信息

    @Target

    指定注解的使用范围,如类、方法、属性、局部属性、参数等, 可以多选。 ```java @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /**
    • Returns an array of the kinds of elements an annotation type
    • can be applied to.
    • @return an array of the kinds of elements an annotation type
    • can be applied to */ ElementType[] value(); }
  1. 具体可选的枚举如下:
  2. ```java
  3. public enum ElementType {
  4. /** 适用范围:类、接口、注解类型,枚举类型enum */
  5. TYPE,
  6. /** 作用于类属性 (includes enum constants) */
  7. FIELD,
  8. /** 作用于方法 */
  9. METHOD,
  10. /** 作用于参数声明 */
  11. PARAMETER,
  12. /** 作用于构造函数声明 */
  13. CONSTRUCTOR,
  14. /** 作用于局部变量声明 */
  15. LOCAL_VARIABLE,
  16. /** 作用于注解声明 */
  17. ANNOTATION_TYPE,
  18. /** 作用于包声明 */
  19. PACKAGE,
  20. /** 作用于类型参数(泛型参数)声明 */
  21. TYPE_PARAMETER,
  22. /** 作用于使用类型的任意语句(不包括class) */
  23. TYPE_USE
  24. }

@Inherited

加上该注解的注解,表示可以被标注的类子类继承,比如A上标记了带有@Inherited的注解,那么类B继承了A, 那么B也会有这个注解,默认情况下注解是不支持继承的。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Inherited {
  5. }

@Document

将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Documented {
  5. }

@Repeatable

1.8中加入的元注解,用来标记是否可以重复标记。

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Repeatable {
  5. /**
  6. * Indicates the <em>containing annotation type</em> for the
  7. * repeatable annotation type.
  8. * @return the containing annotation type
  9. */
  10. Class<? extends Annotation> value();
  11. }

内置注解

java提供了一些内置注解,可以配合编译器来检查代码的正确性, 我们可以关注他们的元注解。

@Override

标记当前方法是覆写父类的方法。

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface Override {
  4. }

@Deprecated

标记一个元素为已过期,不要在使用了

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
  4. public @interface Deprecated {
  5. }

@SuppressWarnings

用来关闭编译器输出的警告信息

  1. @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
  2. @Retention(RetentionPolicy.SOURCE)
  3. public @interface SuppressWarnings {
  4. String[] value();
  5. }

@FunctionalInterface

java8中引入,标记是一个函数式接口,也就是说有且只有一个抽象方法的接口

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.TYPE)
  4. public @interface FunctionalInterface {}

自定义注解

注解遵循的格式一般如下:

  1. //元注解部分 xxxx
  2. @Retention(xxxx)
  3. @Target(xxxx)
  4. public @interface 注解名 {
  5. 返回值 属性名() 默认值;
  6. 返回值 属性名() 默认值;
  7. }
  • 返回值支持的类型如下:java的8种基础类型(不支持包装类型)、String、Class、Enum、Annotation、以及上面类型的数组。
  • 默认值可选,非必有。

举个项目中自定义的栗子:

  1. @Target({ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Inherited
  5. public @interface DataAuthorize {
  6. /**
  7. * 资产ID
  8. * @return
  9. */
  10. String assetId();
  11. /**
  12. * 资产类型
  13. * @return
  14. */
  15. String assetType();
  16. /**
  17. * 权限代码
  18. * @return
  19. */
  20. String authCode() default "";
  21. /**
  22. * 使用的类型
  23. * @return
  24. */
  25. Class[] useType();
  26. }

使用反射操作注解

大部分情况下,我们的项目或者开源框架中都定义了大量的注解,而且都是@Retention(RetentionPolicy.RUNTIME)运行时阶段,我们可以通过反射获取注解中的信息,所以整体遵循下面的一个范式。

  1. 自定义注解
  2. 扫描注解
  3. 通过反射获取注解的信息,执行相应的逻辑。

下面我们重点使用下如何用反射来获取注解的信息。

  1. 定义target是注解的注解 ```java @Inherited @Retention( value = RetentionPolicy.RUNTIME) @Target(value = {ElementType.ANNOTATION_TYPE}) public @interface AnnoTest {

    String value() default “anno”;

}

  1. 2. 定义一个几乎全量信息的注解
  2. ```java
  3. @AnnoTest("alvinAnno")
  4. @Inherited
  5. @Retention( value = RetentionPolicy.RUNTIME)
  6. @Target(value = {ElementType.TYPE_USE,ElementType.PACKAGE,ElementType.FIELD,
  7. ElementType.TYPE_PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
  8. @Documented
  9. public @interface FullAnnoTest {
  10. String value() default "FullAnnoTest";
  11. }
  1. 定义测试类和反射代码 ```java @FullAnnoTest(“package”) package com.alvin.java.core.anno;

public class ParentObj { }

@FullAnnoTest(“testAnnoReflect”) public class TestAnnoReflect<@FullAnnoTest(“parameter”) T > extends @FullAnnoTest(“parent”)ParentObj {

  1. @FullAnnoTest("constructor")
  2. TestAnnoReflect() {
  3. }
  4. //注解字段域
  5. private @FullAnnoTest("name") String name;
  6. //注解泛型字段域
  7. private @FullAnnoTest("value") T value;
  8. //注解通配符
  9. private @FullAnnoTest("list") List<@FullAnnoTest("generic") ?> list;
  10. //注解方法
  11. @FullAnnoTest("method") //注解方法参数
  12. public String hello(@FullAnnoTest("methodParameter") String name)
  13. throws @FullAnnoTest("Exception") Exception { // 注解抛出异常
  14. //注解局部变量,现在运行时暂时无法获取(忽略)
  15. @FullAnnoTest("result") String result;
  16. result = "siting";
  17. System.out.println(name);
  18. return result;
  19. }
  20. public static void main(String[] args) throws Exception {
  21. TestAnnoReflect<String> TestAnnoReflect = new TestAnnoReflect<> ();
  22. Class<TestAnnoReflect<Object>> clazz = (Class<TestAnnoReflect<Object>>) TestAnnoReflect.getClass();
  23. //class的注解
  24. Annotation[] annotations = clazz.getAnnotations();
  25. FullAnnoTest testTmp = (FullAnnoTest) annotations[0];
  26. System.out.println("修饰TestAnnoReflect.class注解value: "+testTmp.value());
  27. //构造器的注解
  28. Constructor<TestAnnoReflect<Object>> constructor = (Constructor<TestAnnoReflect<Object>>) clazz.getDeclaredConstructors()[0];
  29. testTmp = constructor.getAnnotation(FullAnnoTest.class);
  30. System.out.println("修饰构造器的注解value: "+testTmp.value());
  31. //继承父类的注解
  32. AnnotatedType annotatedType = clazz.getAnnotatedSuperclass();
  33. testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
  34. System.out.println("修饰继承父类的注解value: "+testTmp.value());
  35. //注解的注解
  36. AnnoTest AnnoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
  37. System.out.println("修饰注解的注解AnnoTest-value: "+AnnoTest.value());
  38. //泛型参数 T 的注解
  39. TypeVariable<Class<TestAnnoReflect<Object>>> variable = clazz.getTypeParameters()[0];
  40. testTmp = variable.getAnnotation(FullAnnoTest.class);
  41. System.out.println("修饰泛型参数T注解value: "+testTmp.value());
  42. //普通字段域 的注解
  43. Field[] fields = clazz.getDeclaredFields();
  44. Field nameField = fields[0];
  45. testTmp = nameField.getAnnotation(FullAnnoTest.class);
  46. System.out.println("修饰普通字段域name注解value: "+testTmp.value());
  47. //泛型字段域 的注解
  48. Field valueField = fields[1];
  49. testTmp = valueField.getAnnotation(FullAnnoTest.class);
  50. System.out.println("修饰泛型字段T注解value: "+testTmp.value());
  51. //通配符字段域 的注解
  52. Field listField = fields[2];
  53. AnnotatedParameterizedType annotatedPType = (AnnotatedParameterizedType)listField.getAnnotatedType();
  54. testTmp = annotatedPType.getAnnotation(FullAnnoTest.class);
  55. System.out.println("修饰泛型注解value: "+testTmp.value());
  56. //通配符注解 的注解
  57. AnnotatedType[] annotatedTypes = annotatedPType.getAnnotatedActualTypeArguments();
  58. AnnotatedWildcardType annotatedWildcardType = (AnnotatedWildcardType) annotatedTypes[0];
  59. testTmp = annotatedWildcardType.getAnnotation(FullAnnoTest.class);
  60. System.out.println("修饰通配符注解value: "+testTmp.value());
  61. //方法的注解
  62. Method method = clazz.getDeclaredMethod("hello", String.class);
  63. annotatedType = method.getAnnotatedReturnType();
  64. testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
  65. System.out.println("修饰方法的注解value: "+testTmp.value());
  66. //异常的注解
  67. annotatedTypes = method.getAnnotatedExceptionTypes();
  68. testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
  69. System.out.println("修饰方法抛出错误的注解value: "+testTmp.value());
  70. //方法参数的注解
  71. annotatedTypes = method.getAnnotatedParameterTypes();
  72. testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
  73. System.out.println("修饰方法参数注解value: "+testTmp.value());
  74. //包的注解
  75. Package p = Package.getPackage("com.alvin.java.core.anno");
  76. testTmp = p.getAnnotation(FullAnnoTest.class);
  77. System.out.println("修饰package注解value: "+testTmp.value());
  78. TestAnnoReflect.hello("hello");
  79. }

}

  1. 4. 查看对应的执行结果
  2. ```java
  3. 修饰TestAnnoReflect.class注解value: testAnnoReflect
  4. 修饰构造器的注解value: constructor
  5. 修饰继承父类的注解value: parent
  6. 修饰注解的注解AnnoTest-value: alvinAnno
  7. 修饰泛型参数T注解value: parameter
  8. 修饰普通字段域name注解value: name
  9. 修饰泛型字段T注解value: value
  10. 修饰泛型注解value: list
  11. 修饰通配符注解value: generic
  12. 修饰方法的注解value: method
  13. 修饰方法抛出错误的注解value: Exception
  14. 修饰方法参数注解value: methodParameter
  15. 修饰package注解value: package
  16. hello

注解的本质和底层实现

大家有没有想过注解的本质是什么?
我们先通过反编译查看注解生成的字节码,可以通过javap -v FullAnnoTest.class查看如下:
image.png
可以看到,我们的注解是继承自Annotation接口。

  1. public interface Annotation {
  2. boolean equals(Object obj);
  3. int hashCode();
  4. String toString();
  5. /**
  6. * Returns the annotation type of this annotation.
  7. * @return the annotation type of this annotation
  8. */
  9. Class<? extends Annotation> annotationType();
  10. }

所以注解相当于一个语法糖一样,可以方便我们使用,本质上它是继承自Annotation的一个接口。
那大家有没有想过它的实现类在哪里?比如下面的代码,获取到注解,按照上面的解释,它是一个接口,那调用value()方法时,它具体调用的哪个实现类呢?我们并没有写实现类啊…..
答案当然就是动态代理生成的实现类。

  1. AnnoTest annoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
  2. System.out.println("修饰注解的注解AnnoTest-value: "+annoTest.value());

我们可以在启动参数添加如下命令可以查看生成的代理类:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
image.png
执行后,生成代理类如下,
image.png
代理大致的代码如下:

  1. public final class $Proxy2 extends Proxy implements FullAnnoTest {
  2. private static Method m1;
  3. private static Method m2;
  4. private static Method m4;
  5. private static Method m0;
  6. private static Method m3;
  7. public $Proxy2(InvocationHandler var1) throws {
  8. super(var1);
  9. }
  10. public final boolean equals(Object var1) throws {
  11. try {
  12. return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
  13. } catch (RuntimeException | Error var3) {
  14. throw var3;
  15. } catch (Throwable var4) {
  16. throw new UndeclaredThrowableException(var4);
  17. }
  18. }
  19. public final Class annotationType() throws {
  20. try {
  21. return (Class)super.h.invoke(this, m4, (Object[])null);
  22. } catch (RuntimeException | Error var2) {
  23. throw var2;
  24. } catch (Throwable var3) {
  25. throw new UndeclaredThrowableException(var3);
  26. }
  27. }
  28. public final String value() throws {
  29. try {
  30. return (String)super.h.invoke(this, m3, (Object[])null);
  31. } catch (RuntimeException | Error var2) {
  32. throw var2;
  33. } catch (Throwable var3) {
  34. throw new UndeclaredThrowableException(var3);
  35. }
  36. }
  37. static {
  38. try {
  39. m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
  40. m2 = Class.forName("java.lang.Object").getMethod("toString");
  41. m4 = Class.forName("com.alvin.java.core.anno.FullAnnoTest").getMethod("annotationType");
  42. m0 = Class.forName("java.lang.Object").getMethod("hashCode");
  43. m3 = Class.forName("com.alvin.java.core.anno.FullAnnoTest").getMethod("value");
  44. } catch (NoSuchMethodException var2) {
  45. throw new NoSuchMethodError(var2.getMessage());
  46. } catch (ClassNotFoundException var3) {
  47. throw new NoClassDefFoundError(var3.getMessage());
  48. }
  49. }
  50. }

我们看value()方法,这里调用了super.h对象,也就是InvocationHandler对象,而我们注解用的是AnnotationInvocationHandler这个子类,我们在invoke方法中打个断点,就明白了~~

参考

https://www.cnblogs.com/ziph/p/13056092.html
https://blog.csdn.net/KingBoyWorld/article/details/105337011
https://segmentfault.com/a/1190000027073489