java注解學(xué)習(xí)(2)-利用注解生成動(dòng)態(tài)代碼

前言

在前面我們學(xué)習(xí)過java的注解的語法胚膊,說明了注解的作用豺撑;前面舉的例子是運(yùn)行時(shí)注解娃循,這樣會(huì)影響我們代碼執(zhí)行的效率;本章我們看看編譯時(shí)注解的使用方式窖剑,可以利用注解在編譯時(shí)生成代碼跃脊,已提高我們的工作效率;其關(guān)鍵就在AbstractProcessor(注解處理器)

示例

廢話不多說苛吱,我們直接看一個(gè)簡(jiǎn)單的示例:

注解處理器

java library

首先創(chuàng)建一個(gè)java library酪术,用來創(chuàng)建我們的注解處理器
Gradle module java-library

apply plugin: 'java-library'



dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    api 'com.google.auto.service:auto-service:1.0-rc3'
}

tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

定義一個(gè)我們的自定義注解

package com.example.testabstractprocessor;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE) //編譯時(shí)起作用
@Target(ElementType.FIELD) //注解作用在field
public @interface TestBindView {
    int value() default -1;
}

針對(duì)上面的自定義注解,編寫注解處理器

package com.example.testabstractprocessor;

import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;


@AutoService(Processor.class)
public class TestAbstractProcessor extends AbstractProcessor {

    private Filer mFiler;//用于生成java文件
    private Messager mMessager; //用于生成日志
    private Elements mElements; //用于解讀annation注解對(duì)象的相關(guān)信息的輔助類

    public void note(String log){
        mMessager.printMessage(Diagnostic.Kind.WARNING,log);
    }

    public void note(String format,Object ...args){
        mMessager.printMessage(Diagnostic.Kind.WARNING,String.format(format,args));
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        /*
        該方法返回一個(gè)Set<String>翠储,代表ButterKnifeProcessor要處理的注解類的名稱集合绘雁,即 ButterKnife 支持的注解:butterknife-annotations
        * */
        //表示,自定義的annotation的名稱集合
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(TestBindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        //返回當(dāng)前系統(tǒng)支持的 java 版本
        return SourceVersion.latestSupported();
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        /*
        首先 init() 方法完成sdk版本的判斷以及相關(guān)幫助類的初始化援所,幫助類主要有以下幾個(gè):
        Elements elementUtils庐舟,注解處理器運(yùn)行掃描源文件時(shí),以獲取元素(Element)相關(guān)的信息住拭。Element 有以下幾個(gè)子類:
        包(PackageElement)挪略、類(TypeElement)、成員變量(VariableElement)滔岳、方法(ExecutableElement)
        //總之杠娱,是用來表示被標(biāo)注的信息
        Types typeUtils,
        Filer filer谱煤,用來生成 java 類文件摊求。
        Trees trees,
        **/
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mMessager = processingEnvironment.getMessager();
        mElements = processingEnvironment.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //完成了目標(biāo)類信息的收集并生成對(duì)應(yīng) java 類
        Set<? extends Element> TestBindViewElements = roundEnvironment.getElementsAnnotatedWith(TestBindView.class);
        for(Element element:TestBindViewElements){
            //獲取包名
            PackageElement packageElement = mElements.getPackageOf(element);
            String pkgName = packageElement.getQualifiedName().toString();
            note("package name = %s",pkgName);

            //獲得包裝類類名
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            String enclosingName = typeElement.getQualifiedName().toString();
            note("Enclosing class name = %s",enclosingName);

            //TestBindView是針對(duì)view進(jìn)行注解的
            String fieldName = ((VariableElement)element).getSimpleName().toString(); //field的名字
            String fieldClassType = ((VariableElement)element).asType().toString(); //field的類型
            TestBindView testBindView = element.getAnnotation(TestBindView.class);
            int fieldValue = testBindView.value(); //注解的數(shù)據(jù)
            note("field %s %s = %d",fieldClassType,fieldName,fieldValue);

            //生成輔助文件
            createFile(typeElement,fieldClassType,fieldName,fieldValue);
            return true;

        }
        return false;
    }

    public void createFile(TypeElement enclosingClass,String fieldType,String fieldName,int fieldValue){
        String pkgName = mElements.getPackageOf(enclosingClass).getQualifiedName().toString();
        String enclosingName = enclosingClass.getQualifiedName().toString();
        try {
            JavaFileObject javaFileObject = mFiler.createSourceFile(enclosingName+"_TestBindView"
                                                                   ,new Element[]{}); //要生成的java文件
            Writer writer =  javaFileObject.openWriter();
            writer.write(brewCode(pkgName,fieldType,fieldName,fieldValue));
            writer.flush();
            writer.close(); //創(chuàng)建對(duì)應(yīng)的java文件

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String brewCode(String pkgName,String fieldType,String fieldName,int fieldValue){
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkgName + ";\n\n");
        builder.append("http://Auto generated by AbstractProcessor,do not modify!!\n\n");
        builder.append("public class MainActivity_TestBindView { \n\n");
        builder.append("public static void main(String[] args){ \n");
        String info = String.format("%s %s = %d", fieldType, fieldName, fieldValue);
        builder.append("System.out.println(\"" + info + "\");\n");
        builder.append("}\n");
        builder.append("}");
        return builder.toString();

    }
}

注意點(diǎn)1:
@AutoService(Processor.class) :向javac注冊(cè)我們這個(gè)自定義的注解處理器刘离,這樣室叉,在javac編譯時(shí),才會(huì)調(diào)用到我們這個(gè)自定義的注解處理器方法硫惕。
AutoService這里主要是用來生成
META-INF/services/javax.annotation.processing.Processor文件的茧痕。如果不加上這個(gè)注解,那么恼除,你需要自己進(jìn)行手動(dòng)配置進(jìn)行注冊(cè)踪旷,具體手動(dòng)注冊(cè)方法如下:
1.創(chuàng)建一個(gè)
META-INF/services/javax.annotation.processing.Processor文件,
其內(nèi)容是一系列的自定義注解處理器完整有效類名集合,以換行切割
2.將自定義注解處理器和
META-INF/services/javax.annotation.processing.Processor打包成一個(gè).jar文件埃脏。所以其目錄結(jié)構(gòu)大概如下所示:

MyProcessor.jar
    - com
        - example
            - MyProcessor.class

    - META-INF
        - services
            - javax.annotation.processing.Processor

注意點(diǎn)2:
init(ProcessingEnvironment env):每個(gè)Annotation Processor必須***
有一個(gè)空的構(gòu)造函數(shù) *搪锣。編譯期間秋忙,init()會(huì)自動(dòng)被注解處理工具調(diào)用彩掐,并傳入ProcessingEnviroment參數(shù),通過該參數(shù)可以獲取到很多有用的工具類: Elements (用于存儲(chǔ)被注解的信息), Types , Filer(用于生成java文件) **等等
process(Set<? extends TypeElement> annoations, RoundEnvironment roundEnv):Annotation Processor掃描出的結(jié)果會(huì)存儲(chǔ)進(jìn)roundEnv中灰追,可以在這里獲取到注解內(nèi)容堵幽,編寫你的操作邏輯。注意,process()函數(shù)中不能直接進(jìn)行異常拋出,否則的話,運(yùn)行Annotation Processor的進(jìn)程會(huì)異常崩潰,然后彈出一大堆讓人捉摸不清的堆棧調(diào)用日志顯示.
getSupportedAnnotationTypes(): 該函數(shù)用于指定該自定義注解處理器(Annotation Processor)是注冊(cè)給哪些注解的(Annotation),注解(Annotation)指定必須是完整的包名+類名(eg:com.example.MyAnnotation)
getSupportedSourceVersion():用于指定你的java版本弹澎,一般返回:SourceVersion.latestSupported()朴下。當(dāng)然,你也可以指定具體java版本:
return SourceVersion.RELEASE_7;

經(jīng)過前面3個(gè)步驟后苦蒿,其實(shí)就已經(jīng)算完成了自定義Annotation Processor殴胧。后面要做的就是在源碼里面,在需要的地方寫上我們自定義的注解就行了佩迟。
以上注解處理器就使用完畢团滥,講生成的jar文件cp到app/libs文件夾下,在app端使用

app

Gradle module app

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    annotationProcessor files('libs/TestAbstractProcessor.jar')
}
package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import com.example.testabstractprocessor.TestBindView;

public class MainActivity extends AppCompatActivity {

   @TestBindView(R.id.textView)
    TextView tv; //此處對(duì)TextView使用了注解

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

參考注解處理器(Annotation Processor)簡(jiǎn)析
注意點(diǎn):

  1. 在 app/build/generated/source/apt/debug下面生成了想要的文件
package com.example.myapplication;

//Auto generated by AbstractProcessor,do not modify!!

public class MainActivity_TestBindView { 

public static void main(String[] args){ 
System.out.println("android.widget.TextView tv = 2131165319");
}

2.執(zhí)行g(shù)radlew build
就可以在Android Studio下的terminal中看到
警告: package name = com.example.myapplication
警告: Enclosing class name = com.example.myapplication.MainActivity
警告: field android.widget.TextView tv = 2131165319
通過Messager在編譯期間打出log

踩坑

總結(jié)下踩過得坑

首先在Android Stuido里面創(chuàng)建一個(gè)java library module

https://blog.csdn.net/u013087553/article/details/71747327

使用創(chuàng)建java library

https://www.cnblogs.com/jpfss/p/9875402.html

java生成jar包
META-INF https://www.cnblogs.com/mq0036/p/8566427.html

錯(cuò)誤: 編碼GBK的不可映射字符
https://www.cnblogs.com/fply/p/8327759.html

module:app

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    api project(path: ':TestAbstractProcessor')
}

這樣配置不行
Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.

Gradle 3.0以后灸姊,先將生成的jar包復(fù)制過去app project下面的libs文件夾

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    annotationProcessor files('libs/TestAbstractProcessor.jar')
}

拓展延伸

以上其實(shí)就是ButterKnife的基本原理力惯;也是利用注解處理器父晶,對(duì)自定義的注解生成對(duì)應(yīng)的java文件诱建;具體可參考ButterKnife 原理解析
總結(jié)來說就是注解處理器+JavaPoet(更方便的生成java文件)
生成類似于

// Generated code from Butter Knife. Do not modify!
package com.example.test1;

import android.support.annotation.CallSuper;
import android.support.annotation.UiThread;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;

public class MainActivity_ViewBinding<T extends MainActivity> implements Unbinder {
  protected T target;

  private View view2131165218;

  @UiThread
  public MainActivity_ViewBinding(final T target, View source) {
    this.target = target;

    View view;
    target.test = Utils.findRequiredViewAsType(source, R.id.test, "field 'test'", TextView.class);
    view = Utils.findRequiredView(source, R.id.button, "field 'btn' and method 'btn_click'");
    target.btn = Utils.castView(view, R.id.button, "field 'btn'", Button.class);
    view2131165218 = view;
    view.setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View p0) {
        target.btn_click();
      }
    });
  }

  @Override
  @CallSuper
  public void unbind() {
    T target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");

    target.test = null;
    target.btn = null;

    view2131165218.setOnClickListener(null);
    view2131165218 = null;

    this.target = null;
  }
}

總結(jié)

注解一般有運(yùn)行時(shí)處理和編譯時(shí)處理兩種使用方式,這里我們都學(xué)習(xí)了一遍押袍,可以看出谊惭,注解對(duì)于一些大型框架圈盔,效率工具而言,是一種利器铁蹈;希望有一天自己能根據(jù)工作中的需求用到注解的使用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末众眨,一起剝皮案震驚了整個(gè)濱河市娩梨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌颂龙,老刑警劉巖纽什,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稿湿,死亡現(xiàn)場(chǎng)離奇詭異饺藤,居然都是意外死亡涕俗,警方通過查閱死者的電腦和手機(jī)再姑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門绍填,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讨永,“玉大人遇革,你說我怎么就攤上這事揭糕。” “怎么了锻霎?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旋恼。 經(jīng)常有香客問我,道長蚌铜,這世上最難降的妖魔是什么锨侯? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任冬殃,我火速辦了婚禮,結(jié)果婚禮上叁怪,老公的妹妹穿的比我還像新娘审葬。我一直安慰自己奕谭,他們只是感情好涣觉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般员淫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上介返,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天圣蝎,我揣著相機(jī)與錄音,去河邊找鬼衡瓶。 笑死徘公,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鞍陨。 我是一名探鬼主播步淹,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼从隆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了缭裆?” 一聲冷哼從身側(cè)響起键闺,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎澈驼,沒想到半個(gè)月后辛燥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缝其,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年挎塌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片内边。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡榴都,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漠其,到底是詐尸還是另有隱情嘴高,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布和屎,位于F島的核電站拴驮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柴信。R本人自食惡果不足惜套啤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望随常。 院中可真熱鬧潜沦,春花似錦、人聲如沸线罕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钞楼。三九已至喇闸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間询件,已是汗流浹背燃乍。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宛琅,地道東北人刻蟹。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像嘿辟,于是被迫代替她去往敵國和親舆瘪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子片效,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 這個(gè)夏天,酷暑近乎猖狂 似一個(gè)無賴英古,懶著不走 這些日子淀衣,夫夷江不再豐滿 如一個(gè)清秀的姑娘 那些池塘是很容易倒影的 ...
    白云之外閱讀 353評(píng)論 0 1
  • 版權(quán)說明:本篇文章由"貓科龍"翻譯膨桥,譯者已獲得原作者Translate及Republish授權(quán)。版權(quán)所有唠叛,未經(jīng)譯者...
    貓科龍閱讀 682評(píng)論 2 1
  • 多哄哄自己只嚣,沒什么大不了,都是你想出來的…… 窮也會(huì)讓我更敏銳 喜歡她的話艺沼,餓一點(diǎn)册舞,慢慢把微飽當(dāng)成一種狀態(tài),而不是撐澳厢。
    黏玉米閱讀 182評(píng)論 0 0
  • 十里春風(fēng)环础,不如你的播出囚似,不僅吸引了許多的年輕人剩拢,也讓老一輩的人對(duì)年輕時(shí)代的回想。里面的情節(jié)吸引了大多數(shù)的人饶唤,...
    傻蛋么么閱讀 231評(píng)論 0 0
  • 萊斯徐伐, 9歲時(shí),圣誕節(jié)到了募狂,別家孩子都得到家長贈(zèng)送的禮 物 办素, 有跑車,有新衣服祸穷,有家電…… 而萊斯的父親卻給...
    旖旎i閱讀 292評(píng)論 0 2