[78→100]編譯時(shí)Annotation的處理流程

神奇的說(shuō)明——Java Annotation(注解)中介紹了如何在運(yùn)行時(shí)透過(guò)反射的方式獲取注解信息。
那么編譯時(shí)如何獲取注解信息呢逝撬?
其實(shí)定铜,編譯時(shí) Annotation 指 @Retention 為 CLASS 的 Annotation,是由 APT(Annotation Processing Tool) 自動(dòng)解析的。APT在編譯時(shí)根據(jù)resources資源文件夾下的META-INF/services/javax.annotation.processing.Processor自動(dòng)查找所有繼承自 AbstractProcessor 的類哄芜,然后調(diào)用他們的 process 方法去處理。

核心步驟如下:

  1. 新建兩個(gè)注解PrintMePrintMe2
package panda.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface PrintMe {
}
package panda.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
public @interface PrintMe2 {
}
  1. 新建編譯時(shí)處理類MyProcessor
package panda.annotation;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
@SupportedAnnotationTypes({"panda.annotation.PrintMe","panda.annotation.PrintMe2"})
public class MyProcessor extends AbstractProcessor {
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        Messager messager = processingEnv.getMessager();
        for (TypeElement te : annotations) {
            messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + te);
            if (te instanceof PrintMe){
                for (Element e : env.getElementsAnnotatedWith(te)) {
                    messager.printMessage(Diagnostic.Kind.NOTE, "\t**Printing: " + e.toString());
                }
            }
            else{
                for (Element e : env.getElementsAnnotatedWith(te)) {
                    messager.printMessage(Diagnostic.Kind.NOTE, "\t--Printing: " + e.toString());
                }
            }

        }
        return true;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}
  1. 為了這個(gè)處理器在編譯時(shí)被調(diào)用柬唯,需要在META-INF中顯示標(biāo)識(shí)认臊。在resources資源文件夾下新建META-INF/services/
    javax.annotation.processing.Processor,把MyProcessor放在里面
panda.annotation.MyProcessor
  1. 新建測(cè)試類锄奢,進(jìn)行注解測(cè)試
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import panda.annotation.PrintMe;
import panda.annotation.PrintMe2;
public class MainActivity extends AppCompatActivity {
    @PrintMe
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    @PrintMe
    @Override
    protected void onStart() {
        super.onStart();
    }
    @PrintMe
    @Override
    protected void onResume() {
        super.onResume();
    }
    @PrintMe2
    @Override
    protected void onPause() {
        super.onPause();
    }
    @PrintMe2
    @Override
    protected void onStop() {
        super.onStop();
    }
    @PrintMe2
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

編譯后失晴,輸入的編譯日志為

注: Printing: panda.annotation.PrintMe
注:  --Printing: onCreate(android.os.Bundle)
注:  --Printing: onStart()
注:  --Printing: onResume()
注: Printing: panda.annotation.PrintMe2
注:  --Printing: onPause()
注:  --Printing: onStop()
注:  --Printing: onDestroy()

解析JsonAnnotation

JsonAnnotation是一個(gè)典型的編譯時(shí)注解庫(kù)。

package kale.net.json.annotation;

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

/**
 * @author Jack Tony
 * @date 2015/8/13
 */
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Json2Model {

    String modelName();
    
    String jsonStr();

    // custom the model's package name
    String packageName() default "";
}

其處理流程為:

  1. 根據(jù)注解拘央,獲得包名涂屁、類名json字符串
  2. 類名json字符串 送入 jsonformat 包灰伟,獲得對(duì)應(yīng)的Model類字符串拆又。
  3. 根據(jù)包名類名Model類字符串栏账,寫入java文件帖族。

核心代碼如下:

package kale.net.json.processor;

import com.jsonformat.JsonParserHelper;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
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;

import kale.net.json.annotation.Json2Model;

/**
 * @author Jack Tony
 * @date 2015/8/13
 */
@SupportedAnnotationTypes({"kale.net.json.annotation.Json2Model"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class Json2ModelProcessor extends AbstractProcessor {

    private static final String TAG = "[ " + Json2Model.class.getSimpleName() + " ]:";

    private Elements elementUtils;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
    }

    private String packageName;

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement te : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
                log("Working on: " + e.toString());
                VariableElement varE = (VariableElement) e;

                Json2Model json2Model = e.getAnnotation(Json2Model.class);
                if (json2Model.packageName().equals("")) {
                    // no custom package name
                    /**
                     *  example:
                     *  String GET_USER_INFO = "create/info/user/info"; 
                     *  result:create/info/user/info
                     */
                    if (varE.getConstantValue() == null) {
                        fatalError("jsonStr couldn't be final");
                    }
                    String url = varE.getConstantValue().toString();
                    packageName = url2packageName(url);
                } else {
                    // has custom package name
                    packageName = json2Model.packageName();
                }
                if (json2Model.jsonStr() == null || json2Model.jsonStr().equals("")) {
                    fatalError("json string is null");
                }

                final String clsName = json2Model.modelName();

                JsonParserHelper helper = new JsonParserHelper();
                helper.parse(json2Model.jsonStr(), clsName, new JsonParserHelper.ParseListener() {
                    public void onParseComplete(String str) {
                        createModelClass(packageName, clsName, "package " + packageName + ";\n" + str);
                    }

                    public void onParseError(Exception e) {
                        e.printStackTrace();
                        fatalError(e.getMessage());
                    }
                });
                log("Complete on: " + e.toString());
            }
        }
        return true;
    }

    private void createModelClass(String packageName, String clsName, String content) {
        //PackageElement pkgElement = elementUtils.getPackageElement("");
        TypeElement pkgElement = elementUtils.getTypeElement(packageName);

        OutputStreamWriter osw = null;
        try {
            // create a model file
            JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(packageName + "." + clsName, pkgElement);
            OutputStream os = fileObject.openOutputStream();
            osw = new OutputStreamWriter(os, Charset.forName("UTF-8"));
            osw.write(content, 0, content.length());

        } catch (IOException e) {
            e.printStackTrace();
            fatalError(e.getMessage());
        } finally {
            try {
                if (osw != null) {
                    osw.flush();
                    osw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                fatalError(e.getMessage());
            }
        }
    }

    private void log(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, TAG + msg);
    }

    private void fatalError(String msg) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, TAG + " FATAL ERROR: " + msg);
    }

    /**
     * /user/test/ - > user.test
     */
    public static String url2packageName(String url) {
        String packageName = url.replaceAll("/", ".");
        if (packageName.startsWith(".")) {
            packageName = packageName.substring(1);
        }
        if (packageName.substring(packageName.length() - 1).equals(".")) {
            packageName = packageName.substring(0, packageName.length() - 1);
        }
        return packageName;
    }

}

思考

編譯時(shí)注解可以將一些固定的代碼隱藏起來(lái),只保留核心邏輯发笔,最典型的應(yīng)用是butterknife
透過(guò)注解BindView把冗余常見(jiàn)的findViewById替換掉了盟萨,最后的代碼結(jié)構(gòu)簡(jiǎn)單清晰,便于理解了讨。

class ExampleActivity extends Activity {
  @BindView(R.id.title) TextView title;
  @BindView(R.id.subtitle) TextView subtitle;
  @BindView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

而json字符串放在java里面捻激,并不是一個(gè)可讀性很強(qiáng)的結(jié)構(gòu),以后字段增減看起來(lái)也不直觀前计。

     String simpleStr = "{\n"
            + "    \"id\": 100,\n"
            + "    \"body\": \"It is my post\",\n"
            + "    \"number\": 0.13,\n"
            + "    \"created_at\": \"2014-05-22 19:12:38\"\n"
            + "}";
    // 簡(jiǎn)單格式
    @Json2Model(modelName = "Simple", jsonStr = simpleStr)
    String TEST_SIMPLE = "test/simple"; // api url

所以這時(shí)候采用編譯時(shí)注解并不太合適胞谭。還是提取一個(gè)jar包,批量將json轉(zhuǎn)成model男杈,以后的增減由GsonFormat單個(gè)操作吧丈屹。

參考

  1. Java 注解
  2. Annotation實(shí)戰(zhàn)【自定義AbstractProcessor】
  3. Java Annotation 及幾個(gè)常用開(kāi)源項(xiàng)目注解原理簡(jiǎn)析

Panda
2016-07-13

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伶棒,隨后出現(xiàn)的幾起案子旺垒,更是在濱河造成了極大的恐慌,老刑警劉巖肤无,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件先蒋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宛渐,警方通過(guò)查閱死者的電腦和手機(jī)竞漾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門眯搭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人业岁,你說(shuō)我怎么就攤上這事鳞仙。” “怎么了笔时?”我有些...
    開(kāi)封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵棍好,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我糊闽,道長(zhǎng)梳玫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任右犹,我火速辦了婚禮,結(jié)果婚禮上姚垃,老公的妹妹穿的比我還像新娘念链。我一直安慰自己,他們只是感情好积糯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布掂墓。 她就那樣靜靜地躺著,像睡著了一般看成。 火紅的嫁衣襯著肌膚如雪君编。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天川慌,我揣著相機(jī)與錄音吃嘿,去河邊找鬼。 笑死梦重,一個(gè)胖子當(dāng)著我的面吹牛兑燥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播琴拧,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼降瞳,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蚓胸?” 一聲冷哼從身側(cè)響起挣饥,我...
    開(kāi)封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沛膳,沒(méi)想到半個(gè)月后扔枫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡于置,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年茧吊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贞岭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搓侄,死狀恐怖瞄桨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讶踪,我是刑警寧澤芯侥,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站乳讥,受9級(jí)特大地震影響柱查,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜云石,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一唉工、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汹忠,春花似錦淋硝、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至铅乡,卻和暖如春继谚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阵幸。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工花履, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侨嘀。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓臭挽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咬腕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子欢峰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

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

  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光劍書架上的書閱讀 3,884評(píng)論 2 8
  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來(lái) What:A...
    zlcook閱讀 29,161評(píng)論 15 116
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,166評(píng)論 0 2
  • 整體Retrofit內(nèi)容如下: 1、Retrofit解析1之前哨站——理解RESTful 2涨共、Retrofit解析...
    隔壁老李頭閱讀 6,495評(píng)論 4 31
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理纽帖,服務(wù)發(fā)現(xiàn),斷路器举反,智...
    卡卡羅2017閱讀 134,657評(píng)論 18 139