Java注解之深入淺出

官方文檔定義:注解是一系列元數(shù)據(jù),它提供數(shù)據(jù)用來(lái)解釋程序代碼,但是注解并非是所解釋的代碼本身的一部分得哆。注解對(duì)于代碼的運(yùn)行效果沒有直接影響。

簡(jiǎn)單來(lái)說(shuō)哟旗,你可以理解為一種標(biāo)簽贩据,例如一提到自己心中的女神,腦海中自然會(huì)為她貼上"beautiful"這個(gè)標(biāo)簽闸餐,但是你的這個(gè)"beautiful"標(biāo)簽并不屬于她本身饱亮,你的這個(gè)標(biāo)簽對(duì)她來(lái)說(shuō)沒有直接影響,你只是條單純的舔狗而已......

好吧舍沙,回到正題近上,注解存在的意義是什么呢?
注解的用處主要如下:

  • 提供信息給編譯器: 編譯器可以利用注解來(lái)探測(cè)錯(cuò)誤和警告信息
  • 編譯階段時(shí)的處理: 軟件工具可以用來(lái)利用注解信息來(lái)生成代碼拂铡、Html文檔或者做其它相應(yīng)處理壹无。
  • 運(yùn)行時(shí)的處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取

還是回到官方文檔的解釋上,注解主要針對(duì)的是編譯器和其它工具軟件(SoftWare tool)感帅。
當(dāng)開發(fā)者使用了Annotation 修飾了類斗锭、方法、Field 等成員之后留瞳,這些 Annotation 不會(huì)自己生效拒迅,必須由開發(fā)者提供相應(yīng)的代碼來(lái)提取并處理 Annotation 信息。這些處理提取和處理 Annotation 的代碼統(tǒng)稱為 APT(Annotation Processing Tool)她倘。
所以呢璧微,注解是給編譯器或APT用的

例如:

public @interface AnnotationTest() {
}

@AnnotationTest
public void Test() {
}

上述代碼簡(jiǎn)單來(lái)說(shuō)就是把AnnotationTest作為一個(gè)標(biāo)簽貼到Test類上面去了
但是實(shí)際上并沒有什么卵用,因?yàn)楦緵]有實(shí)現(xiàn)注解功能

想要注解能夠正常工作硬梁,還是要依賴于元注解這個(gè)東西
元注解是一種基本注解前硫,可以注解到其他的注解上面去
主要有@Retention@Document荧止、@Target屹电、@Inherited阶剑、@Repeatable5種

@Retention

當(dāng)它應(yīng)用到一個(gè)注解上的時(shí)候,說(shuō)明了這個(gè)注解的存活時(shí)間
取值如下:

  • RetentionPolicy.SOURCE: 注解只在源碼階段保留危号,在編譯器進(jìn)行編譯時(shí)它將被丟棄忽視牧愁。
  • RetentionPolicy.CLASS: 注解只被保留到編譯進(jìn)行的時(shí)候,它并不會(huì)被加載到 JVM 中外莲。
  • RetentionPolicy.RUNTIME: 注解可以保留到程序運(yùn)行的時(shí)候猪半,它會(huì)被加載進(jìn)入到 JVM 中,所以在程序運(yùn)行時(shí)可以獲取到它們偷线。
@Retention(RetentionPolicy.CLASS)
public @interface AnnotationTest() {
}

@Document

將被標(biāo)注的注解生成到j(luò)avadoc中

@Target

用于指定被此元注解標(biāo)注的注解可以標(biāo)注的程序元素
可以看一下@Target的源碼

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

可以看到有一個(gè)value屬性磨确,返回一個(gè)枚舉ElementType類型的數(shù)組,這個(gè)數(shù)組的值就代表了可以使用的程序元素声邦。

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
}

@Target取值如下

  • ElementType.TYPE 可以給一個(gè)類型進(jìn)行注解乏奥,比如類、接口亥曹、枚舉
  • ElementType.FIELD 可以給屬性進(jìn)行注解
  • ElementType.METHOD 可以給方法進(jìn)行注解
  • ElementType.PARAMETER 可以給一個(gè)方法內(nèi)的參數(shù)進(jìn)行注解
  • ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
  • ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
  • ElementType.ANNOTATION_TYPE 可以給一個(gè)注解進(jìn)行注解
  • ElementType.PACKAGE 可以給一個(gè)包進(jìn)行注解
  • ElementType.TYPE_PARAMETER標(biāo)明注解可以用于類型參數(shù)聲明(1.8新加入)
  • ElementType.TYPE_USE類型使用聲明(1.8新加入)邓了,可以標(biāo)注除class之外的任意類型
    當(dāng)注解未指定Target值時(shí),則此注解可以用于任何元素之上歇式,多個(gè)值使用{}包含并用逗號(hào)隔開
@Target(value = {ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.TYPE_PARAMETER})

@Inherited

其讓被修飾的注解擁有被繼承的能力,但是它并不是說(shuō)注解本身可以繼承驶悟,而是說(shuō)如果一個(gè)父類被 @Inherited 注解過(guò)的注解進(jìn)行注解的話胡野,那么如果它的子類沒有被任何注解應(yīng)用的話材失,那么這個(gè)子類就繼承了父類的注解。

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.TYPE})
@Inherited
public @interface TestAnnotation {}

@TestAnnotation
public class A {}

public class B extends A{}

也就是說(shuō)B也擁有TestAnnotation 這個(gè)注解

@Repeatable

表示注解可以多次應(yīng)用硫豆,通常是注解的值可以取多個(gè)
一個(gè)很簡(jiǎn)單的例子:我是一個(gè)程序員龙巨,同時(shí)也是美食家和攝影師

@interface Live {
    People[] value();
}

@Repeatable(Live.class)
@interface People {
    String role() default "";
}

@People(role = "programmer")
@People(role = "gastronome")
@People(role = "photographer")
public class Me {
}

上述代碼通過(guò)@Repeatable注解了People ,而@Repeatable 后面括號(hào)中的Live類相當(dāng)于一個(gè)容器注解(A fucking new concept)

容器注解:就是用來(lái)存放其它注解的地方,它本身也是一個(gè)注解

按java規(guī)定熊响,容器注解里面必須要有一個(gè)value屬性旨别,屬性類型是一個(gè)被 @Repeatable 注解過(guò)的注解數(shù)組

注解的屬性也叫成員變量,注解只有成員變量汗茄,沒有方法秸弛,而且成員變量在注解的定義中以“無(wú)形參的方法”的形式來(lái)聲明,方法名定義了成員變量的名字洪碳,返回值定義了成員變量的類型递览。

在注解中定義屬性時(shí)它的類型必須是 8 種基本數(shù)據(jù)類型外加 類、接口瞳腌、注解及它們的數(shù)組

注解中屬性可以有默認(rèn)值绞铃,默認(rèn)值需要用 default 關(guān)鍵值指定,這樣做的好處是可以省略賦值操作

@People()
public class Me {}

如果成員變量?jī)H有一個(gè)名字叫做value的屬性時(shí)嫂侍,你甚至可以這樣寫

@People("programmer")
public class Me {}

如果沒有成員變量的話儿捧,括號(hào)都可以省略喲荚坞!

@People
public class Me {}
Java語(yǔ)言本身也提供了預(yù)置的注解

@Deprecated
這個(gè)元素是用來(lái)標(biāo)記過(guò)時(shí)的元素,會(huì)有一條橫線菲盾,這是編譯器識(shí)別后的提醒效果
@SuppressWarnings
阻止警告的意思颓影。之前說(shuō)過(guò)調(diào)用被 @Deprecated 注解的方法后,編譯器會(huì)警告提醒懒鉴,而有時(shí)候開發(fā)者會(huì)忽略這種警告瞭空,他們可以在調(diào)用的地方通過(guò) @SuppressWarnings 達(dá)到目的。
@SafeVarargs
參數(shù)安全類型注解疗我。它的目的是提醒開發(fā)者不要用參數(shù)做一些不安全的操作,它的存在會(huì)阻止編譯器產(chǎn)生 unchecked 這樣的警告咆畏。它是在 Java 1.7 的版本中加入的。
@FunctionalInterface
函數(shù)式接口注解吴裤,這個(gè)是 Java 1.8 版本引入的新特性旧找。函數(shù)式編程很火,所以 Java 8 也及時(shí)添加了這個(gè)特性麦牺。
函數(shù)式接口 (Functional Interface) 就是一個(gè)具有一個(gè)方法的普通接口钮蛛。
PS:函數(shù)式接口標(biāo)記有什么用,這個(gè)原因是函數(shù)式接口可以很容易轉(zhuǎn)換為 Lambda 表達(dá)式剖膳。

想必上面的理論知識(shí)都能很快理解吧魏颓,接下來(lái)就手撕代碼吧

注解只是給我們想要的東西去貼上一個(gè)標(biāo)簽,那么怎么去閱讀標(biāo)簽?zāi)兀?br> 注解通過(guò)反射獲取
查看一下Class.java源碼吱晒,可以找到isAnnotationPresent()這個(gè)方法,用來(lái)判斷它是否應(yīng)用了某個(gè)注解

    /**
     * {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @Override
    public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return GenericDeclaration.super.isAnnotationPresent(annotationClass);
    }

上面代碼可以看出Annotation接口是所有注解的父接口
此外甸饱,可以通過(guò)getAnnotation(Class<A> annotationClass())獲取指定類型的Annotation對(duì)象

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.5
     */
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().annotations.get(annotationClass);
    }

getAnnotations()可以獲取到這個(gè)元素上的所有類型的Annotation對(duì)象

    /**
     * @since 1.5
     */
    public Annotation[] getAnnotations() {
        return AnnotationParser.toArray(annotationData().annotations);
    }

getDeclaredAnnotation(Class<A> annotationClass)返回該元素上存在的直接修飾該元素的指定類型的注解,如果不存在則返回null.

    /**
     * @throws NullPointerException {@inheritDoc}
     * @since 1.8
     */
    @Override
    @SuppressWarnings("unchecked")
    public <A extends Annotation> A getDeclaredAnnotation(Class<A> annotationClass) {
        Objects.requireNonNull(annotationClass);

        return (A) annotationData().declaredAnnotations.get(annotationClass);
    }

getDeclaredAnnotations()返回該元素上存在的直接修飾該元素的所有注解

    /**
     * @since 1.5
     */
    public Annotation[] getDeclaredAnnotations()  {
        return AnnotationParser.toArray(annotationData().declaredAnnotations);
    }

okay,枯燥無(wú)聊的源碼還有很多仑濒,例如getDeclaredAnnotationsByType()叹话、getAnnotationsByType()等等,接下來(lái)用一個(gè)簡(jiǎn)單的例子通過(guò)反射來(lái)獲取Annotation的屬性
首先墩瞳,定義一個(gè)注解

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    public int id() default 1;
    public String name() default "Kris";
}

然后呢

@TestAnnotation(id = 666,name = "Kris")
public class Me {
    public static void main(String[] args) {
        boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("name:"+testAnnotation.name());
        }
    }
}

okay,接下來(lái)執(zhí)行這個(gè)程序就可以發(fā)現(xiàn):
Amazing!!!


那么驼壶,繼續(xù)加把力唄!

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface A {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface B {
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface C {
}

@TestAnnotation(id = 666,name = "Kris")
public class Me {

    @A(value = "This is A!")
    int a;

    @B
    @C
    public void methodTest() {
    }

    public static void main(String[] args) {
        boolean hasAnnotation = Me.class.isAnnotationPresent(TestAnnotation.class);
        if (hasAnnotation) {
            TestAnnotation testAnnotation = Me.class.getAnnotation(TestAnnotation.class);
            System.out.println("id:"+testAnnotation.id());
            System.out.println("name:"+testAnnotation.name());
        }
        try {
            Field a = Me.class.getDeclaredField("a");
            a.setAccessible(true);
            A annotation = a.getAnnotation(A.class);
            if (annotation != null) {
                System.out.println("A value:"+annotation.value());
            }
            Method methodTest = Me.class.getDeclaredMethod("methodTest");
            if (methodTest != null) {
                Annotation[] ans = methodTest.getAnnotations();
                for (int i=0;i<ans.length;i++) {
                    System.out.println("methodTest Annotation:"+ans[i].annotationType().getSimpleName());
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

顯而易見的得到結(jié)果:


綜上所述喉酌,只要通過(guò)給元素添加注解热凹,再利用反射就可以獲取注解啦,但是獲取到有什么用呢泪电?下面就通過(guò)數(shù)據(jù)庫(kù)SQL創(chuàng)建語(yǔ)句實(shí)例來(lái)講解一下APT(Annotation Process Tool)吧般妙。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
    String name() default "";
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
    boolean primaryKey() default false;
    boolean allowNull() default false;
    boolean unique() default false;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
    String name() default "";
    Constraints constraint() default @Constraints;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
    String name() default "";
    int value() default 0;
    Constraints constraint() default @Constraints;
}

@DBTable(name = "MEMBER")
public class Member {

    @SQLString(name = "ID",value = 50,constraint = @Constraints(primaryKey = true))
    private int id;

    @SQLString(name = "NAME",value = 30)
    private String name;

    @SQLInteger(name = "AGE")
    private int age;

    @SQLString(name = "DESCRIPTION",value = 150,constraint = @Constraints(allowNull = true))
    private String description;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

public class TableCreator {
    public static void main(String[] args) {
        String[] arg={"AnnotationTest.SQLAnnotation.Member"};
        for(String className : arg) {
            System.out.println("Table Creation SQL for " +
                    className + " is :\n" + createTableSql(className));
        }
    }

    private static String createTableSql(String className) {
        Class<?> cl = null;
        DBTable dbTable = null;
        try {
            cl = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (cl != null) {
            dbTable = cl.getAnnotation(DBTable.class);
        }
        if (dbTable == null) {
            System.out.println("No DBTable Annotations int class!");
            return null;
        }
        String tableName = dbTable.name();

        if (tableName.length() == 0) {
            tableName = cl.getName().toUpperCase();
        }
        List<String> columnDefs = new ArrayList<>();
        for (Field field : cl.getDeclaredFields()) {
            String columnName = null;
            if (field.isAnnotationPresent(SQLInteger.class)) {
                SQLInteger sqlInteger = (SQLInteger) field.getDeclaredAnnotation(SQLInteger.class);
                if (sqlInteger.name().length() == 0) {
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlInteger.name();
                }
                columnDefs.add(columnName + " INT" + getConstraints(sqlInteger.constraint()));
            } else if (field.isAnnotationPresent(SQLString.class)) {
                SQLString sqlString = (SQLString) field.getDeclaredAnnotation(SQLString.class);
                if (sqlString.name().length() == 0) {
                    columnName = field.getName().toUpperCase();
                } else {
                    columnName = sqlString.name();
                }
                columnDefs.add(columnName+" VARCHAR(" + sqlString.value() + ")" + getConstraints(sqlString.constraint()));
            }
        }
        StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + "(");
        for (String columnDef : columnDefs) {
            createCommand.append("\n " + columnDef + ",");
        }
        return createCommand.substring(0,createCommand.length()-1)+");";
    }

    private static String getConstraints(Constraints con) {
        StringBuilder constraints = new StringBuilder();
        if (!con.allowNull()) {
            constraints.append(" NOT NULL");
        }
        if (con.primaryKey()) {
            constraints.append(" PRIMARY KEY");
        }
        if (con.unique()) {
            constraints.append(" UNIQUE");
        }
        return constraints.toString();
    }
}

OK,這樣就可以成功得通過(guò)APT得到所需要的SQL語(yǔ)句啦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歪架,一起剝皮案震驚了整個(gè)濱河市股冗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌和蚪,老刑警劉巖止状,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹棉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡怯疤,警方通過(guò)查閱死者的電腦和手機(jī)浆洗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)集峦,“玉大人伏社,你說(shuō)我怎么就攤上這事∷伲” “怎么了摘昌?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)高蜂。 經(jīng)常有香客問(wèn)我聪黎,道長(zhǎng),這世上最難降的妖魔是什么备恤? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任稿饰,我火速辦了婚禮,結(jié)果婚禮上露泊,老公的妹妹穿的比我還像新娘喉镰。我一直安慰自己,他們只是感情好惭笑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布侣姆。 她就那樣靜靜地躺著,像睡著了一般脖咐。 火紅的嫁衣襯著肌膚如雪铺敌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天屁擅,我揣著相機(jī)與錄音,去河邊找鬼产弹。 笑死派歌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的痰哨。 我是一名探鬼主播胶果,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼斤斧!你這毒婦竟也來(lái)了早抠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撬讽,失蹤者是張志新(化名)和其女友劉穎蕊连,沒想到半個(gè)月后悬垃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甘苍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年尝蠕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片载庭。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡看彼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囚聚,到底是詐尸還是另有隱情靖榕,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布顽铸,位于F島的核電站序矩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏跋破。R本人自食惡果不足惜簸淀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毒返。 院中可真熱鬧租幕,春花似錦、人聲如沸拧簸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盆赤。三九已至贾富,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牺六,已是汗流浹背颤枪。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留淑际,地道東北人畏纲。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像春缕,于是被迫代替她去往敵國(guó)和親盗胀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353