Java 注解:注解處理器獲取泛型真實(shí)類型

[TOC]

Java 注解:注解處理器獲取泛型真實(shí)類型

注解 annotation 是 Java 中的一大特性概说,是插入代碼中的元數(shù)據(jù)淹遵。注解的使用能夠大大簡(jiǎn)化代碼的編寫,所以在很多框架中得到了使用,比如 Web 框架 Spring 中的 @Service畅姊、@Resource 注解,比如參數(shù)校驗(yàn)框架 hibernate-validator 中的 @NotNull 等注解吹由。

如何定義注解

定義注解需要用到元注解 meta annotation若未,即描述注解的注解。元注解有以下幾類:

  1. @Target:描述了注解所修飾的對(duì)象范圍倾鲫,比如:類粗合、方法、字段乌昔、以及注解本身等等隙疚。
  2. @Retention:定義注解被保留的時(shí)間長(zhǎng)短,有三種:源文件磕道、class 文件供屉、運(yùn)行時(shí)。
  3. @Documented:表示含有該注解類型的元素(帶有注釋的)會(huì)通過javadoc或類似工具進(jìn)行文檔化溺蕉。
  4. @Inherited:表示注解類型能被自動(dòng)繼承伶丐。

Java 中使用 Class 來定義類,使用 enum 定義枚舉疯特,使用 @interface 來定義注解哗魂。具體定義格式如下:

  public @interface 注解名 { 定義體 }

其中的定義體里可以寫若干方法來表示配置,方法的名稱就是參數(shù)的名稱辙芍,返回值類型就是參數(shù)的類型啡彬,可以通過 default 來聲明參數(shù)的默認(rèn)值。下面給出一個(gè)注解的示例:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AutoParse {
    String key();
    int type() default 0;
}

該注解 AutoParse 可以用于描述 FIELD 即類中字段故硅,在運(yùn)行時(shí)仍然能夠獲取庶灿,不可被繼承(沒有使用 Inherited 注解描述)。

獲取注解信息

注解通常用來描述類吃衅、方法等往踢,那如何獲取一個(gè)方法或類上的注解呢?可以通過 Class 類或 Method 類對(duì)應(yīng)的 getAnnotation 方法獲扰遣恪:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    public Annotation[] getAnnotations();
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
    public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass);
    public Annotation[] getDeclaredAnnotations();
}

public final class Method extends Executable {
    public <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    public Annotation[] getDeclaredAnnotations();
    // 返回方法參數(shù)的注解
    public Annotation[][] getParameterAnnotations();
}

getDeclaredAnnotations 方法只返回直接存在于此元素上的注解峻呕,不會(huì)返回繼承的注解利职;而 getAnnotation 則返回全部注解,包含繼承的瘦癌。

注:Field 也有類似的方法可以獲取注解猪贪。

注解處理器

注解處理器 annotation processor 是 javac 內(nèi)置的編譯時(shí)掃描和處理注解的工具,比較常見的用法是在編譯時(shí)獲取注解信息讯私,動(dòng)態(tài)生成 Java 文件热押。

AbstractProcessor

想要自定義注解處理器需要繼承實(shí)現(xiàn) AbstractProcessor 類:

  1. init 方法入?yún)⑹黔h(huán)境變量 ProcessingEnvironment,從中可以拿到報(bào)告錯(cuò)誤信息的 Messager斤寇,用于生成代碼文件的 Filer桶癣。
  2. process 抽象方法,是我們需要實(shí)現(xiàn)的娘锁,用于處理注解信息的方法牙寞,生成類文件的代碼放這里。
public abstract class AbstractProcessor implements Processor {
    public synchronized void init(ProcessingEnvironment processingEnv) {
        if (initialized)
            throw new IllegalStateException("Cannot call init more than once.");
        Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");

        this.processingEnv = processingEnv;
        initialized = true;
    }
    
    public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);
}

示例:注解處理器獲取泛型信息

Java 中的泛型有一個(gè)類型擦除的概念:

Java中的泛型基本上都是在編譯器這個(gè)層次來實(shí)現(xiàn)的莫秆。在生成的Java字節(jié)碼中是不包含泛型中的類型信息的间雀。使用泛型的時(shí)候加上的類型參數(shù),會(huì)在編譯器在編譯的時(shí)候去掉馏锡。這個(gè)過程就稱為類型擦除雷蹂。

那么如何在運(yùn)行時(shí)獲取某對(duì)象的某個(gè) Field 的真實(shí)類型信息呢?既然是在編譯期間擦除的杯道,那么我們就可以注解處理器在編譯期間獲取泛型的真實(shí)類型信息。

以下給出一個(gè)處理上面的 @AutoParse 注解的注解處理器示例责蝠,該注解處理器獲取了注解所描述字段的類型信息党巾,并將這些信息寫入了一個(gè)類文件中:

  1. @SupportedAnnotationTypes:指明要處理的注解。
  2. @SupportedSourceVersion(SourceVersion.RELEASE_7):指明適合的 Java 版本霜医。
  3. 繼承 process 方法齿拂,做真正的處理工作。
  4. Filer 用于創(chuàng)建新的 Java 類肴敛。
  5. RoundEnvironment 類包含了被注解所描述的 Field 的真實(shí)類型信息署海。
@SupportedAnnotationTypes({ "com.albon.arith.annotation.procecssor.AutoParseField" })
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoParseFieldProcessor extends AbstractProcessor {

    private Filer filer;

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

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Map<String, String> fieldTypeMap = Maps.newHashMap();

        for (Element elem : roundEnv.getElementsAnnotatedWith(AutoParseField.class)) {
            AutoParseField annotation = elem.getAnnotation(AutoParseField.class);
            String message = System.currentTimeMillis() + " - annotation found in " + elem.getSimpleName()
                    + " with key " + annotation.key() + " kind " + elem.getKind() + " class " + elem.getClass()
                    + " asType " + elem.asType() + "\n\tgetEnclosingElement().getSimpleName() "
                    + elem.getEnclosingElement().getSimpleName() + "\n\tgetEnclosingElement().asType() "
                    + elem.getEnclosingElement().asType();
            fieldTypeMap.put(elem.getEnclosingElement().asType().toString() + "#" + elem.getSimpleName(),
                    elem.asType().toString());

            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message);
        }

        if (fieldTypeMap.isEmpty()) {
            return true;
        }

        Writer writer = null;
        try {
            JavaFileObject jfo = filer
                    .createSourceFile("com.albon.arith.annotation.service.AutoParseFieldInfo");
            writer = jfo.openWriter();
            writer.write("package com.albon.arith.annotation.service;\n" +
                    "\n" +
                    "import com.google.common.collect.Maps;\n" +
                    "\n" +
                    "import java.util.Map;\n" +
                    "\n" +
                    "public class AutoParseFieldInfo {\n" +
                    "\n" +
                    "    // key: classpath#fieldName, value: fieldType\n" +
                    "    public static final Map<String, String> FIELD_TYPE_MAP = Maps.newHashMap();\n" +
                    "\n" +
                    "    static {\n");

            for (Map.Entry<String, String> entry : fieldTypeMap.entrySet()) {
                writer.write("        FIELD_TYPE_MAP.put(\"" +
                        entry.getKey() +
                        "\", \"" +
                        entry.getValue() +
                        "\");\n");
            }

            writer.write("    }\n" +
                    "\n" +
                    "    public static void main(String[] args) {\n" +
                    "        System.out.println(FIELD_TYPE_MAP);\n" +
                    "    }\n" +
                    "}\n");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return true; // no further processing of this annotation type
    }
}

我們寫了一個(gè)使用 @AutoParse 注解描述的 SimplePoJo 類:

public class SimplePoJo {

    @AutoParse(key = "first")
    private Integer first;
    @AutoParse(key = "second")
    private String second;
    @AutoParse(key = "third")
    private List<String> third;
}

還需要在配置文件中指定使用該注解處理器,請(qǐng)?jiān)?resources 文件夾下新建 META-INF/services 文件夾医男,再新建文件 javax.annotation.processing.Processor砸狞,文件內(nèi)容如下:

com.albon.arith.annotation.procecssor.AutoParseFieldProcessor

代碼完成之后,使用 mvn clean package 進(jìn)行編譯镀梭,編譯后可以看到經(jīng)由注解處理器生成的 AutoParseFieldInfo.java 文件刀森,其內(nèi)容如下所示:

public class AutoParseFieldInfo {
    // key: classpath#fieldName, value: fieldType
    public static final Map<String, String> FIELD_TYPE_MAP = Maps.newHashMap();

    static {        FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#third", "java.util.List<java.lang.String>");
        FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#second", "java.lang.String");
        FIELD_TYPE_MAP.put("com.albon.arith.annotation.service.SimplePoJo#first", "java.lang.Integer");
    }

    public static void main(String[] args) {
        System.out.println(FIELD_TYPE_MAP);
    }
}

示例代碼地址

完整的示例代碼請(qǐng)看 GitHub: annotation-processor

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末报账,一起剝皮案震驚了整個(gè)濱河市研底,隨后出現(xiàn)的幾起案子埠偿,更是在濱河造成了極大的恐慌,老刑警劉巖榜晦,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冠蒋,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡乾胶,警方通過查閱死者的電腦和手機(jī)抖剿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胚吁,“玉大人牙躺,你說我怎么就攤上這事⊥蠓觯” “怎么了孽拷?”我有些...
    開封第一講書人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)半抱。 經(jīng)常有香客問我脓恕,道長(zhǎng),這世上最難降的妖魔是什么窿侈? 我笑而不...
    開封第一講書人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任炼幔,我火速辦了婚禮,結(jié)果婚禮上史简,老公的妹妹穿的比我還像新娘乃秀。我一直安慰自己,他們只是感情好圆兵,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開白布跺讯。 她就那樣靜靜地躺著,像睡著了一般殉农。 火紅的嫁衣襯著肌膚如雪刀脏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評(píng)論 1 290
  • 那天超凳,我揣著相機(jī)與錄音愈污,去河邊找鬼。 笑死轮傍,一個(gè)胖子當(dāng)著我的面吹牛暂雹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播金麸,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼擎析,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揍魂,我...
    開封第一講書人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤桨醋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后现斋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜最,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年庄蹋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞬内。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡限书,死狀恐怖虫蝶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情倦西,我是刑警寧澤能真,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站扰柠,受9級(jí)特大地震影響粉铐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卤档,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一蝙泼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧劝枣,春花似錦汤踏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至琢唾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盾饮,已是汗流浹背采桃。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丘损,地道東北人普办。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像徘钥,于是被迫代替她去往敵國和親衔蹲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

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

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,129評(píng)論 15 116
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一種元程序中的元素關(guān)聯(lián)任何信息和...
    九尾喵的薛定諤閱讀 3,149評(píng)論 0 2
  • Java 中的注解(Annotation) 是一個(gè)很方便的特性在Spring當(dāng)中得到了大量的應(yīng)用 , 我們也可以開...
    _秋天閱讀 8,811評(píng)論 3 22
  • 糟了,剛才被那只該死的貓嚇得我不知道把手機(jī)扔哪兒去了舆驶。好氣俺鹘 !臭貓沙廉,壞貓拘荡,爛貓,我特么刨你家祖墳了撬陵?珊皿!我想回過...
    落荒的桃子閱讀 158評(píng)論 0 0
  • 邏輯狗是陳小冠幼兒園使用的數(shù)學(xué)啟蒙教材。每次拿回家的卡片都會(huì)看一看巨税,從小班開始到現(xiàn)在大班蟋定,難度越來越大,特別是現(xiàn)在...
    木木sani閱讀 337評(píng)論 0 0