注解的使用

665 阅读7分钟

注解的定义

定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。 常见的注解如下图所示: 注解样例.png

注解主要作用

  1. 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@see @param @return 等
  2. 跟踪代码依赖性,实现替代配置文件功能,替代xml的作用,SpringMVC中比较多少使用。
  3. 在编译时进行格式检查,如 @Overrider @CallSuper @Nullable  和 @NonNull等
  4. Android中常见用法 1)以及资源注解@LayoutRes、@IntegerRes, @StringRes、@ColorRes等 2)权限注解@RequiresPermission 3)进程类注解@UiThread,@BinderThread,@MainThread,@WorkerThread 4)ButterKnife 、EventBus、Dagger2框架

元注解:

那么注解是怎么定义的呢,我们首先看下常用的Override 注解。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

这里就涉及到元注解,我们一般用元注解来定义注解,元注解主要有以下几个。下面我们会一一进行介绍。

  1. @Target
  2. @Retention
  3. @Documented
  4. @Inherited
  5. @Reptable JAVA8(新增)

@Target

描述注解的使用范围(即:被修饰的注解可以用在什么地方)。主要为以下类型:

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

@Documented

描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

@Inherited

Inherited注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。

@Retention

Retention注解取值都在RetentionPolicy中

public enum RetentionPolicy {
   SOURCE,
   CLASS,
   RUNTIME
}
  1. SOURCE:在源文件中有效(即源文件保留), @Override, @SuppressWarnings都属于这类注解;
  2. CLASS:在class文件中有效(即class保留)    
  3. RUNTIME:在运行时有效(即运行时保留)

RetentionPolicy.Source

我们来看下RetentionPolicy.Source 的使用场景:由于在编译的过程中这个注解还被保留着,所以在编译过程中可以针对这个policy进行一些操作。

  1. 枚举类型 有时候我们会使用Enum来定义枚举,对于枚举来说占用的内存往往是使用静态常量的两倍,但是我们更推荐用注解定义枚举类型。 使用方式如下:
  @IntDef({Color.RED , Color.YELLOW ,Color.BLUE})
  @Retention(RetentionPolicy.SOURCE)
   public @interface Color{
    int RED = 1;
    int YELLOW = 2;
    int BLUE= 3;
   }
  1. lombok类似场景 还有一种场景是比如在自动生成java代码的场景下使用。最常见的就是lombok的使用了,可以自动生成field的get和set方法以及toString方法,构造器等 详细实现方式及原理见:www.jianshu.com/p/fc06578e8…

RetentionPolicy.Class

CLASS:在class文件中有效(即class保留)。 编译时注解注解处理器的实现主要依赖于AbstractProcessor来实现,这个类是在javax.annotation.processing包中,同时为了我们自己生成java源文件方便,我们还需要引入一些第三方库,主要包括 javapoet 用于生成java源文件,可参考github.com/square/java… auto-service 主要用于生成一些辅助信息,例如META-INF/services 一些信息等. 典型案例框架:Butterknife 基本原理如下: Butterknife原理.png

注解处理器

首先来了解下什么是注解处理器,注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。 你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。一个注解的注解处理器,以java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。

自定义RetentionPolicy.Class 注解场景

这里仅介绍AbstractProcessor相关接口,后续后做自定义注解的说明。

public class MyProcessor extends AbstractProcessor {


   private Elements mElementsUtils;

   /**
    * 注解初始化接口,一般用于做一些初始化准备操作;
    * <p>
    * processingEnvironment 提供了一些工具
    * 包括操作元素、打印信息、文件管理器以及其他工具
    *
    * @param processingEnvironment
    */
   @Override
   public synchronized void init(ProcessingEnvironment processingEnvironment) {
       super.init(processingEnvironment);
       // 元素操作工具
       Elements elementUtils = processingEnvironment.getElementUtils();
       mElementsUtils = elementUtils;

       // 用于创建java源文件或者class文件
       Filer filer = processingEnvironment.getFiler();
       // 区域信息
       Locale locale = processingEnvironment.getLocale();
       // 传递给注释处理工具的特定于处理器的选项
       Map<String, String> options = processingEnvironment.getOptions();
       //任何生成的 source和 class文件应符合的源版本
       SourceVersion sourceVersion = processingEnvironment.getSourceVersion();
       //一些用于对类型进行操作的实用程序方法的实现
       Types typeUtils = processingEnvironment.getTypeUtils();
       //返回用于报告错误,警告和其他通知的消息。
       Messager messager = processingEnvironment.getMessager();
   }

   /**
    * 注解处理接口,注解真正实现的逻辑
    * 我们可以通过JavaPoet 生成java源文件
    *
    * @param annotations      请求处理的注释类型
    * @param roundEnvironment 有关当前和上一轮信息的环境
    * @return 如果返回true, 声明注释类型,并且不会要求后续处理器处理它们; 如果返回false ,则注释类型无人认领,可能会要求后续处理器处理它们
    */
   @Override
   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
       //如果在前一轮处理中引发错误,则返回true ; 否则返回false
       boolean b = roundEnvironment.errorRaised();

       // 返回前一轮生成的注释处理的root elements
       Set<? extends Element> rootElements = roundEnvironment.getRootElements();
       //如果此轮生成的类型不受后续轮注释处理的影响,则返回true ; 否则返回false 。
       boolean b1 = roundEnvironment.processingOver();

       // 返回使用给定注释类型注释的元素。
       Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyAnnotations.class);
       for (Element element : elements) {
           TypeElement typeElement = (TypeElement) element;

           //获取注解的值
           MyAnnotations annotation = typeElement.getAnnotation(MyAnnotations.class);
           int value = annotation.value();

           //获取此类型元素的完全限定名称
           String s = typeElement.getQualifiedName().toString();

           //获取当前注解的包路径
           PackageElement packageElement = mElementsUtils.getPackageOf(typeElement);
           String packageName = packageElement.getQualifiedName().toString();

       }

       return true;
   }


   /**
    * 此处理器支持的注释类型的名称
    *
    * @return
    */
   @Override
   public Set<String> getSupportedAnnotationTypes() {
       // 本处理器仅处理MyAnnotations的注解
       Set<String> supportedAnnotationTypes = new HashSet<>();
       supportedAnnotationTypes.add(MyAnnotations.class.getCanonicalName());
       return supportedAnnotationTypes;
   }


   /**
    * 返回此注释处理器支持的最新源版本
    *
    * @return
    */
   @Override
   public SourceVersion getSupportedSourceVersion() {
       return super.getSupportedSourceVersion();
   }


   @Target(ElementType.PACKAGE)
   @Retention(RetentionPolicy.CLASS)
   private @interface MyAnnotations {
       int value();
   }

}

RetentionPolicy.RUNTIME

RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

注解在各种元素的提供了一些注解相关的操作,包括Class、Method、Field等。我们分别来看下他们提供了哪些注解相关的方法,主要是 AnnotatedElement接口。

    /**
     * 如果此元素上 存在指定类型的注解,则返回true,否则返回false
     */
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

   /**
     * 如果此元素上存在指定注释类型,则返回此元素的注释,否则为null
     */
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    /**
     * 返回此元素上存在的注释。 如果没有存在于此元素上注解,返回值是长度为0这种方法的调用者可以随意修改返回的数组的数组; 它对返回给其他调用者的数组没有影响。
     */
    Annotation[] getAnnotations();

    /**
     * 返回与此元素关联的注释。 如果没有与此元素关联的注释,则返回值是长度为0的数组。
     */
    <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) ;

    /**
     * 如果直接存在这样的注释,则返回指定类型的此元素的注释,否则返回null。 此方法忽略继承的注释。
     */
    <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) ;

    /**
     * 如果此类注释直接存在或间接存在 ,则返回指定类型的此元素的注释。
     *此方法忽略继承的注释。此方法与[`getDeclaredAnnotation(Class)`] ) 
     * 之间的区别在于此方法检测其参数是否为*可重复注释类型*

     */
    <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass);

    /**
     * 返回直接出现在此元素上的注释。 此方法忽略继承的注释。 如果此元素上没有直接存在注释,则返回值为长度为0的数组
     */
    Annotation[] getDeclaredAnnotations();

以下为简单的使用样例

@AnnotationDemo.TypeRuntimeAnnotation(11)
public class AnnotationDemo {

    private static final String TAG = "AnnotationDemo";


    public static void testAnnotationDemo() {
        Log.d(TAG, "testAnnotationDemo() called");
        Class<AnnotationDemo> testAnnotationsClass = AnnotationDemo.class;

        //查找class的注解
        TypeRuntimeAnnotation[] annotations = new TypeRuntimeAnnotation[0];
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            annotations = testAnnotationsClass.getAnnotationsByType(TypeRuntimeAnnotation.class);
        }
        for (TypeRuntimeAnnotation annotation : annotations) {
            int value = annotation.value();
            Log.d(TAG,"TypeRuntimeAnnotation: " + value);
        }

        //寻找注解的方法
        Method[] declaredMethods = testAnnotationsClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            MethodRuntimeAnnotation annotation = declaredMethod.getAnnotation(MethodRuntimeAnnotation.class);
            if (annotation != null) {
                Log.d(TAG, "declaredMethod: " + declaredMethod.getName());
                boolean value = annotation.value();
                Log.d(TAG, "annotation value: " + value);
            }
        }


        //寻找注解的域
        Field[] declaredFields = testAnnotationsClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            FieldRuntimeAnnotation annotation = declaredField.getAnnotation(FieldRuntimeAnnotation.class);
            if (annotation != null) {
                Log.d(TAG, "declaredMethod: " + declaredField.getName());
                int value = annotation.value();
                Log.d(TAG, "annotation value: " + value);
            }
        }
    }


    @FieldRuntimeAnnotation(1)
    public int mValue;


    @MethodRuntimeAnnotation(true)
    public void testMethodRuntimeAnnotation() {

    }

    /**
     * 方法注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MethodRuntimeAnnotation {
        boolean value();
    }


    /**
     * 域注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FieldRuntimeAnnotation {
        int value();
    }


    /**
     * 类,接口注解
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TypeRuntimeAnnotation {
        int value();
    }
}