Annotation註解(一)- 基礎

關於註解,你須要知道的一些知識:
Annotation註解(一)- 基礎
Annotation註解(二)- 進階 - 自定義ButterKnifejava

這篇博客,主要講解關於註解的一些基本知識,包括註解的概念、分類、做用,常見註解的定義及其解析方式等。android

尊重原創,轉載請註明出處 https://segmentfault.com/a/11...
本文出自 強哥大天才的博客segmentfault

Annotation的概念

1. 概念

關於Annotation註解的概念,咱們能夠看下官方的解釋:數組

Annotations, a form of metadata;
provide data about a program that is not part of the program itself.
Annotations have no direct effect on the operation of the code they annotateapp

Annotation註解是Java中的一種元數據,它能夠往源代碼中添加額外的數據,而且對註解的代碼無直接影響;包名、類、成員方法、成員屬性、參數均可以被註解。框架

2. 做用

註解可讓咱們的代碼更加簡潔,而且增長代碼的複用性,避免冗餘代碼。
若是細分起來,註解主要有3個做用:ide

  • 編譯時標記:在編譯時,經過標記代碼,來提示信息、檢查錯誤
  • 編譯時處理:在編譯時,經過構建代碼,來動態生成一些額外的代碼、或者Java/XML等文件
  • 運行時處理:在運行時,咱們能夠動態的獲取註解信息

3. 分類

1. 標準Annotation
標準Annotation是指Java自帶的Annotation。
eg:@Override[重寫]、@Deprecated[不鼓勵使用]、@SuppressWarnings[忽略警告]等。工具

2. 元Annotation
元Annotation是指註解Annotation的Annotation。
eg:@Retention, @Target, @Inherited, @Documented等。性能

3. 自定義Annotation
咱們能夠利用元Annotation來自定義一些本身的Annotation。gradle

元Annotation

元Annotation是指註解Annotation的Annotation。

  • @Documented:Annotation是否會保存到JavaDoc文檔中
  • @Inherited:Annotation是否能夠被繼承,默認是false
  • @Target(ElementType[] types):Annotation能夠用來註解哪些元素

ElementType:元素類型,好比類、方法、變量、參數等
clipboard.png

  • @Retention(RetentionPolicy ploicy):Annotation的保留策略

RetentionPolicy.SOURCE:Annotation僅保留在java源碼中
- 大都爲Mark Annotation,作一個警示、校驗的做用
RetentionPolicy.CLASS:Annotation保留在java源碼、class文件中
- 默認的保留策略,通常配合Processor註解處理器,在構建時動態生成代碼
RetentionPolicy.RUNTIME:Annotation保留在java源碼、class文件、運行時
- 與CLASS的區別是,這類Annotation在運行時會被加載到JVM中,所以咱們能夠在程序運行的過程當中動態獲取到Annotation的相關信息
clipboard.png

自定義Annotation

咱們先來看一下一個最爲基礎的Annotation示例代碼(這是一個自動生成類頭信息的一個註解)

調用

@ClassInfo (
   author = "xzqbetter@163.com",
   date = "11/17/2017",
   currentRevision = 6,
   reviewers = {"Alice", "Bob", "Cindy"}    // Note array notation
)
public class Test1 {
    // class code goes here
}

定義

public @interface ClassInfo {
   String author();
   String date();
   int currentRevision() default 1;
   String[] reviewers();                    // Note use of array
}

解釋

1.註解的定義:能夠看到,咱們利用@interface,定義了一個註解類,以後就能夠在代碼中使用這個註解

  • @interface是interface的一種變異形式

2.參數的定義:在註解類內部,咱們聲明瞭不少抽象方法,來定義註解所需的參數

  • 對應關係:方法名 - 參數名,方法的返回值 - 參數類型
  • 返回值的限制:只能是基本類型、String、Class、Enumeration、Annotation及其一維數組
  • 方法的限制:這些方法沒有方法體、沒有參數、不能拋異常,而且只能用public abstract進行修飾
  • 能夠用default來指定默認值
  • 若是隻有一個參數,能夠用value()代替(在使用的時候無需寫參數名)
  • 能夠利用classInfo.author()來獲取參數值

解析Annotation

完成自定義Annotation後,咱們還須要知道,針對這些註解,咱們要作哪些相關的處理,這就涉及到了Annotation的解析操做。
解析Annotation,一般分爲:對運行時Annotation的解析、對編譯時Annotation的解析;
解析Annotation,其實就是如何從代碼中找到Annotation,一般咱們的作法是:

  • 反射的方式獲取Annotation,運行時Annotation的解析方式
  • 藉助apt工具獲取Annotation,編譯時Annotation的解析方式
  • 另外若是咱們須要生成額外的代碼、文件,則還須要藉助JavaPoet API

反射

反射的解析方式,一般運用在運行時Annotation的解析。
反射是指:利用Class、Field、Method、Construct等reflect對象,獲取Annotation:

  • field.getAnnotation(Annotation.class):獲取某個Annotation
  • field.getAnnotations():獲取全部的Annotation
  • field.isAnnotationPresent(Annotation.class):是否存在該Annotation

這樣,咱們就能夠在程序運行過程當中,動態的獲取Annotation的信息。

藉助反射進行解析,這在必定程度上會影響程序性能

APT工具

概念

APT:是一個註解處理工具 Annotation Processing Tool
做用:利用apt,咱們能夠找到源代中的註解,並根據註解作相應的處理

  • 根據註解,生成額外的源文件或其餘文件
  • 編譯生成的源文件和原來的源文件,一塊兒生成class文件

利用APT,在編譯時生成額外的代碼,不會影響性能,只是影響項目構建的速度

Android Studio配置

使用apt工具前,須要對gradle作一些基本的配置:
1.在project的build.gradle中,添加apt的依賴

dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}

2.在module的build.gradle中,使用apt插件

apply plugin: 'com.neenbedankt.android-apt'

JavaPoet

JavaPoet:是一個自動生成Java代碼的Java API。
在使用前,咱們須要導入相關的依賴:

compile 'com.squareup:javapoet:1.7.0'

JavaPoet主要有如下幾個關鍵類:

  • JavaFile:生成Java文件
  • TypeSpec:定義類
  • MethodSpec:定義方法

MethodSpec

MethodSpec主要用來定義方法。

MethodSpec mainSpec = MethodSpec.methodBuilder("main")         // 方法名
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)            // 修飾符
    .addParameter(String[].class, "args")                      // 參數
    .returns(TypeName.VOID)                                    // 返回值
    .addStatement("$T.out.println($S)", System.class, "Hello,World!!")  // 具體代碼
    .build();

// 添加代碼的方法
mainBuilder.addCode("System.out.println(\"Hello,World!\")");
mainBuilder.addStatement("$T.out.println($S)",System.class,"Hello, World!");
mainBuilder.addStatement("activity.$L= ($T) activity.findViewById($L)",
          element, ClassName.get(member.asType()), injectViewAnno.value() );
// 這裏的element表示一個成員變量, injectViewAnno是一個Annotation, value是註解的參數值

方法名:methodBuilder()
修飾符:addModifiers(Modifier)
返回值:return(TypeName)
參數:addParameter(Class, name)
添加代碼:addStatement末尾會自動添加換行符,addCode末位不會自動添加換行符
-$T:表示須要import的類
-$S:表示字符串,會自動添加雙引號
-$L:表示變量,不帶雙引號

TypeSpec

TypeSpec主要用來定義類。

// 外部類
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld")         // 類名
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)              // 修飾符
    .addMethod(mainSpec)                                        // 方法
    .build();
// 內部類
TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("innerClass")    // 內部類的類名
    .addSuperinterface(OutClass.class)                              // 外部類的Class
    .build();

JavaFile

JavaFile是真正生成Java文件的核心類。

try {
    JavaFile javaFile = JavaFile.builder("com.example.seven", typeSpec).build();    // 指定包名+類
    javaFile.writeTo(processingEnv.getFiler());                                     // 固定寫法
} catch (IOException e) {
    e.printStackTrace();
}

運行時Annotation

運行時Annotation,是指@Retention爲RetentionPolicy.RUNTIME的註解。
對於這類註解,咱們一般配合反射進行解析。

運行時Annotation的解析,須要藉助反射進行解析,這在必定程度上會影響程序性能。
早期的依賴注入框架,大都屬於運行時處理。

示例代碼

public static void main(String[] args) {
    try {
        Class clazz = Class.forName("com.example.Runtime");
        for (Method method : clazz.getMethods()) {
            MethodAnno methodAnno = method.getAnnotation(
MethodAnno.class);
            if (methodAnno != null) {
                System.out.println("method name:" + method.getName());
                System.out.println("method author:" + methodAnno.author());
                System.out.println("method version:" + methodAnno.version());
                System.out.println("method date:" + methodAnno.date());
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

編譯時Annotation

編譯時Annotation,是指@Retention爲RetentionPolicy.CLASS的註解。
對於這類註解,咱們一般藉助apt工具註解處理器Processor進行解析。

編譯時Annotation,利用apt工具,在編譯時,自動生成一些額外的代碼,相比運行時Annotation更加高效。

定義一個編譯時Annotation的標準流程以下:

  1. 定義註解:@interface
  2. 定義註解處理器:重寫AbstractProcessor的2個方法
  3. 實現註解處理器:JavaPoet
  4. 註冊註解處理器:@AutoService
  5. 使用註解,build工程

下面,咱們將一一進行講解

1. 定義註解

參考,上面的【自定義註解】部分

2. 定義註解處理器

概念

註解處理器是一個繼承自AbstractProcessor的一個Java類,內部主要定義了:

  • 要處理的註解是誰:經過getSupportedAnnotationTypes方法進行指定
  • 如何處理這個註解:經過process方法進行指定

注意:AbstractProcessor只能用在Java項目中,在Android項目中沒法使用,由於Android內部刪除了這個類;所以若是想要在Android項目中,使用這個類,須要新建一個Java Library庫。

示例代碼

@AutoService(Processor.class)
public class AutoCreateProcessor extends AbstractProcessor {
    
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 返回的Set集合,是註解類ClassName的集合
        // AutoCreate是一個自定義的Annotation
        return Collections.singleton(AutoCreate.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 利用JavaPoet進行相關處理
        return false;
    }
    
}

解釋

getSupportedAnnotationTypes

  • 這個方法主要用來指定,當前的註解處理器,須要處理哪些Annotation
  • 這些Annotation,將會經過一個Set集合的形式返回
  • Set集合內部存儲的是,全部註解的類名className

process

  • 這個方法主要用來定義,針對這些註解,咱們要作哪些相應的處理
  • 一般,咱們會在方法內部,利用JavaPoet建立相關的Java類、或是XML文件
  • 返回值表示,當前Processor是否接收這組Annotation,若是接收,後續其餘的Processor將不會繼續處理

3. 實現註解處理器

實現註解處理器,主要是利用JavaPoet對process部分的代碼進行完善補充,在下一個博客中,咱們將會詳細介紹。

4. 註冊註解處理器

註冊註解處理器,就是告訴APT這個類是一個Annotation Processor
咱們能夠藉助AutoService進行快速註冊:

  • 添加AutoService的依賴
  • 在類上註解 @AutoService(Processor.class),完成註冊
compile 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Processor.class)

5. build工程

完成以上步驟,咱們build下工程,就會在build目錄下自動生成對應的java/class文件

  • java文件在build/generated文件夾下
  • class文件在build/intermediates文件夾下

clipboard.pngclipboard.png

相關文章
相關標籤/搜索