注解使用方法

157 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情

Java注解(Annotation)是JDK 5.0中引入的一种机制,用于对类,方法,参数和包等进行标注,Java中常见的@Override,@Deprecated等标识就是注解。

一些常见注解定义如下所示:

 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.SOURCE)
 public @interface Override {
 }
 ​
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
 public @interface SafeVarargs {}
 ​
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 public @interface Inherited {
 }
 ​
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 public @interface Documented {
 }
 ​
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 public @interface Retention {
     /**
      * Returns the retention policy.
      * @return the retention policy
      */
     RetentionPolicy value();
 }
 ​
 @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();
 }

可以看出注解是一个使用@interface修饰的接口类,在其上方使用@XXX标记,按照用途和使用方法来讲,注解主要分为三类:

  • 元注解:元注解指的是为构建注解环境而定义的基础注解,上述代码中的@Documented和@Inherited都是元注解,元注解的@target为ElementType.ANNOTATION_TYPE,基础的元注解有以下几类:

    注解名称注解说明备注
    @Documented文档类型注解,可以将其标注的元素添加到javadoc中,上述代码中,SafeVarargs,Inherited,Documented均使用了该注解,说明这几个接口都会被添加到javadoc内/
    @InheritedInherited是注解继承的意思,主要说的是当注解A使用@Inherited定义时,B类使用注解A,如果其子类C没有注解时,会自动继承父类的注解,也就是父类和子类的注解一致/
    @RepeatableJDK 1.8引入的新的元注解,代表在同一类上,该注解可多次使用/
    @Retention用于定义注解在编译或者运行过程中如何存储,其取值主要包含三类: 1.RetentionPolicy.SOURCE 表示被标记的注解,仅仅只在代码级被保留,编译器检查使用; 2.RetentionPolicy.CLASS 该标记表示被标记的注解会被编译器保留,但仅仅在编译期保留,但是会被JVM忽略; 3.RetentionPolicy.RUNTIME 该标记的注解,表示会被JVM保留,可以在运行时被使用; 一般情况下,我们对Retention使用RetentionPolicy.RUNTIME,注解括号里面传入的就是注解取值,有点类似于函数参数,在Retention的定义中可以看到value()的声明/
    @TargetTarget用于定义注解的作用类型,比如注解只能针对方法使用,类和方法都可以使用等,其取值如下所示: 1. ElementType.ANNOTATION_TYPE 应用于一个注解类型; 2. ElementType.CONSTRUCTOR 应用于构造函数 3. ElementType.FIELD 应用于类的成员变量 4. ElementType.LOCAL_VARIABLE 应用于局部变量 5. ElementType.METHOD 应用于方法 6. ElementType.PACKAGE 应用于包 7. ElementType.PARAMETER 应用于方法的参数 8. ElementType.TYPE 应用于类/
  • JDK内置注解

    JDK内置注解说的是一些Java内部以定义的注解类型,如@Override等,JDK已定义的注解如下所示:

    注解名称说明存储方式作用域是否可重复声明是否可继承备注
    @Override标记该方法是重写父类方法RetentionPolicy.SOURCE,编译时方法/
    @Deprecated标记被弃用,后续不再维护RetentionPolicy.RUNTIME,运行时所有Target类型均可用/
    @SuppressWarnings用于抑制编译器警告,使用SuppressWarnings修饰后,编辑器不会再提示相关告警,其取值为String类型,常见用法为@SuppressWarnings("unchecked")RetentionPolicy.SOURCE,编译时类元素,成员变量,方法,参数,构造器,局部变量/
    @SafeVarargsJDK 7 专门为抑制堆污染警告提供的RetentionPolicy.RUNTIME,运行时构造器,方法/
    @FunctionalInterfaceJava 8中新增的函数时接口,如果接口中只有一个抽象方法(允许包含多个default或static方法),则该接口被称为函数式接口RetentionPolicy.RUNTIME,运行时类元素/
  • 自定义注解

    自定义注解指的是开发者根据自己需求,自己使用元注解创建的注解类,自定义注解往往用于实现特定功能,比如Dagger2和Hilt,其内部注解可以帮助完成依赖注入过程,Arouter中的注解用于实现路由协议等

自定义注解实现流程

一般情况下,自定义注解需要三步:

  1. 使用元注解定义注解,类似于上文中代码
  2. 在需要注解的地方声明注解
  3. 解析注解

接下来我们来按照步骤定义一个在函数上使用的运行时注解,该注解接受字符串作为取值。

使用元注解定义注解

 import java.lang.annotation.*;
 ​
 // 可以加入javadoc中
 @Documented
 // 运行时可以使用的注解
 @Retention(RetentionPolicy.RUNTIME)
 // 注解作用在方法上
 @Target(ElementType.METHOD)
 public @interface HelloAnnotation {
     /**
      * 注解取值,返回String字符串
      */
     String value();
 }

按照上文知识及我们的目标定义注解如上所示

声明注解

创建TestHelloAnnotation.java类,在其内部定义函数,使用@HelloAnnotation注解,代码如下:

 public class TestHelloAnnotation {
 ​
     @HelloAnnotation("this is hello method annotation value")
     public void hello() {
     }
 }

解析注解

对于注解而言,在获取其值或者调用注解函数时,需要使用反射执行,注解不同于普通java类,自定义注解更多的用来解决一些机械化的工作,通常情况下搭配APT(Annotation Processor Tools)以及gradle插件使用。

我们在main函数中通过反射获取TestHelloAnnotation中hello函数的注解,代码如下:

 Class testHelloAnnotationClass = TestHelloAnnotation.class;
 // 获取hello函数
 Method helloMethod = testHelloAnnotationClass.getMethod("hello");
 // 判断hello函数是否用HelloAnnotation修饰
 if (helloMethod.isAnnotationPresent(HelloAnnotation.class)) {
     // 获取hello函数的注解对象
     HelloAnnotation helloAnnotation1 = helloMethod.getAnnotation(HelloAnnotation.class);
     // 打印hello函数注解对象的value取值
     System.out.println(helloAnnotation1.value());
 }

运行结果如下:

1-7-1-1

注解+APT在各大开源库中有较多应用,需要重视,比如ButterKnife,Arouter,Dagger2,Hilt等,都有注解的影子

Retention三种类型的应用场景及区别

Retention级别技术说明典型示例
RetentionPolicy.SOURCE保留在源代码APT在编译期获取注解以及使用注解的类的所有信息,一般用于做编译时检查或者生成辅助类,信息收集等ButterKnife,ARouter,Tinker等
RetentionPolicy.CLASS保留在class文件中,JVM忽略字节码增强在编译出class文件后,通过修改class文件实现修改代码逻辑的目的,通过与ASM等结合使用AspectJ,Roubust
RetentionPolicy.RUNTIME保留在class文件中,可运行时访问反射在运行时,可以通过反射获取类的注解和元素,以完成不同的代码逻辑/