用自定義注解做點(diǎn)什么

請(qǐng)看前言


你不一定聽(tīng)過(guò)注解序无,但你一定對(duì)@Override不陌生械念。

當(dāng)我們重寫(xiě)父類方法的時(shí)候我們就看到了@Override。我們知道它表示父類方法被子類重寫(xiě)了。

現(xiàn)在告訴你婉商,@Override就是一個(gè)注解似忧。

也許你會(huì)疑惑注解是什么?
注解(annotation)是JDK5之后引進(jìn)的新特性丈秩,是一種特殊的注釋盯捌,之所以說(shuō)它特殊是因?yàn)椴煌谄胀ㄗ⑨專╟omment)能存在于源碼,而且還能存在編譯期跟運(yùn)行期蘑秽,會(huì)最終編譯成一個(gè).class文件饺著,所以注解能有比普通注釋更多的功能。
簡(jiǎn)單來(lái)說(shuō)肠牲,注解是一個(gè)比春藥還猛的東西幼衰。

接下來(lái),先入個(gè)門(mén)缀雳,然后通過(guò)實(shí)戰(zhàn)來(lái)證明注解有多“猛”渡嚣。

PS : 如果已經(jīng)了解的小伙伴可自行跳到 自定義注解實(shí)戰(zhàn)。

自定義注解入門(mén)


我們對(duì)于注解的認(rèn)識(shí)大多數(shù)來(lái)源于標(biāo)準(zhǔn)注解(也稱為內(nèi)建注解)肥印。

標(biāo)準(zhǔn)注解 表示的意義
@Override 用于標(biāo)識(shí)該方法繼承自超類
當(dāng)父類的方法被刪除或修改了识椰,編譯器會(huì)提示錯(cuò)誤信息
@Deprecated 表示該類或者該方法已經(jīng)不推薦使用
如果用戶還是要使用,會(huì)生成編譯的警告
@SuppressWarnings 用于忽略的編譯器警告信息

Java不僅僅提供我們?cè)械淖⒔馐褂檬溃€允許我們自定義注解裤唠。比如你可以像這樣:

public @interface DoSomething {
    public String name() default "write";
}

這是最簡(jiǎn)單的注解聲明挤牛。
盡管看上去像是接口的寫(xiě)法莹痢,但完全不是一回事。這一點(diǎn)要注意墓赴。
而使用注解也很簡(jiǎn)單竞膳,可以像這樣:

@DoSomething(name = "walidake")//可以顯式傳值進(jìn)來(lái),此時(shí)name=walidake
public class UseAnnotation {

}

@DoSomething//如果不傳值诫硕,則默認(rèn)name=我們定義的默認(rèn)值坦辟,即我們上面定義的"write"
public class UseAnnotation {

}

需要注意的是當(dāng)注解有value()方法時(shí),不需要指明具體名稱章办。

public @interface DoSomething {
    public String value();
    public String name() default "write";
}

@DoSomething("walidake")
public class UseAnnotation {

}

然而“最簡(jiǎn)單的自定義注解”并沒(méi)有特別的意義锉走。所以,這時(shí)候我們需要引入一個(gè)元注解的概念藕届。

我們需要知道這些概念:
“普通注解”只能用來(lái)注解“代碼”挪蹭,而“元注解”只能用來(lái)注解 “普通注解”
自定義注解是“普通注解”休偶。

JDK5時(shí)支持的元注解有@Documented @Retention @Target @Inherited梁厉,接下來(lái)分別介紹它們修飾注解的效果。

@Documented
@interface DocumentedAnnotation{

}

@interface UnDocumentedAnnotation{

}

@DocumentedAnnotation
@UnDocumentedAnnotation
public class UseDocumentedAnnotation{

}

打開(kāi)小黑窗踏兜,運(yùn)行javadoc UseDocumentedAnnotation.java

運(yùn)行結(jié)果:

documented.png

結(jié)論:可以看到词顾,被@Documented修飾的注解會(huì)生成到j(luò)avadoc中八秃,如@DocumentedAnnotation。
而不被@Documented修飾的注解(@UnDocumentedAnnotation)不會(huì)生成到j(luò)avadoc中肉盹。

注解的級(jí)別
@Retention可以設(shè)置注解的級(jí)別昔驱,分為三種,都有其特定的功能上忍。
這個(gè)元注解是我們關(guān)注的重點(diǎn)舍悯,后面實(shí)戰(zhàn)我們會(huì)用到。

注解級(jí)別 存在范圍 主要用途
SOURCE 源碼級(jí)別 注解只存在源碼中 功能是與編譯器交互睡雇,用于代碼檢測(cè)萌衬。
如@Override,@SuppressWarings。
額外效率損耗發(fā)生在編譯時(shí)
CLASS 字節(jié)碼級(jí)別 注解存在源碼與字節(jié)碼文件中 主要用于編譯時(shí)生成額外的文件它抱,如XML秕豫,Java文件等,但運(yùn)行時(shí)無(wú)法獲得观蓄。
這個(gè)級(jí)別需要添加JVM加載時(shí)候的代理(javaagent)混移,使用代理來(lái)動(dòng)態(tài)修改字節(jié)碼文件
RUNTIME 運(yùn)行時(shí)級(jí)別 注解存在源碼,字節(jié)碼與Java虛擬機(jī)中 主要用于運(yùn)行時(shí)反射獲取相關(guān)信息

限制注解使用的范圍
注解默認(rèn)可以修飾各種元素侮穿,而使用@Target可以限制注解的使用范圍歌径。

例如,可以限定注解只能修飾方法亲茅。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {

}

上面的代碼將注解的使用范圍限制在了方法上回铛,而不能用來(lái)修飾類。

試著用@Override修飾類會(huì)得到“The annotation @Override is disallowed for this location”的錯(cuò)誤克锣。

@Target支持的范圍(參見(jiàn)ElementType):

1) 類茵肃,接口,注解;
2) 屬性域袭祟;
3) 方法验残;
4) 參數(shù);
5) 構(gòu)造函數(shù)巾乳;
6) 局部變量您没;
7) 注解類型;
8) 包

注解的繼承
@Inherited可以讓注解類似被“繼承”一樣胆绊。
通過(guò)使用@Inherited氨鹏,可以讓子類對(duì)象使用getAnnotations()獲取父類@Inherited修飾的注解。

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Inheritable{

}

@interface UnInheritable{

}

public class UseInheritedAnnotation{
    @UnInheritable
    @Inheritable
    public static class Super{
    
    }

    public static class Sub extends  Super {
    
    }

    public static void main(String... args){
    
        Super instance=new Sub();
        //result : [@com.walidake.annotation.util.Inheritable()]
        System.out.println(Arrays.toString(instance.getClass().getAnnotations()));
    }
}

我們干脆用@Documented查看類結(jié)構(gòu)辑舷。發(fā)現(xiàn):

inherit1.png
inherit2.png

這是不是恰恰證明了這種是偽繼承的說(shuō)法喻犁,而不是真正的繼承。

自定義注解實(shí)戰(zhàn)##


引言
Java Web開(kāi)發(fā)中,對(duì)框架的理解和掌握是必須的肢础。而在使用大多數(shù)框架的過(guò)程中还栓,一般有兩種方式的配置,一種是基于xml的配置方式传轰,一種是基于注解的方式剩盒。然而,越來(lái)越多的程序員(我)在開(kāi)發(fā)過(guò)程中享受到注解帶來(lái)的簡(jiǎn)便慨蛙,并義無(wú)反顧地投身其中辽聊。

ORM框架,像Hibernate期贫,Mybatis就提供了基于注解的配置方式跟匆。我們接下來(lái)就使用自定義注解實(shí)現(xiàn)袖珍版的Mybatis,袖珍版的Hibernate通砍。

這很重要
說(shuō)明:實(shí)戰(zhàn)的代碼會(huì)被文章末尾附上玛臂。而實(shí)際上在之前做袖珍版框架的時(shí)候并沒(méi)有想到會(huì)拿來(lái)做自定義注解的Demo。因此給出的代碼涉及了其他的一些技術(shù)封孙,例如數(shù)據(jù)庫(kù)連接池迹冤,動(dòng)態(tài)代理等等,比較雜虎忌。
在這個(gè)篇幅我們只討論關(guān)于自定義注解的問(wèn)題泡徙,至于其他的技術(shù)后面會(huì)開(kāi)多幾篇博文闡述。(當(dāng)然這么多前輩面前不敢造次膜蠢,有個(gè)討論學(xué)習(xí)的氛圍是很好的~)

那么在自定義注解框架前堪藐,我們需要花點(diǎn)時(shí)間瀏覽以下幾個(gè)和Annotation相關(guān)的方法。

方法名 用法
Annotation getAnnotation(Class annotationType) 獲取注解在其上的annotationType
Annotation[] getAnnotations() 獲取所有注解
isAnnotationPresent(Class annotationType) 判斷當(dāng)前元素是否被annotationType注解
Annotation[] getDeclareAnnotations() 與getAnnotations() 類似狡蝶,但是不包括父類中被Inherited修飾的注解

Mybatis 自定義注解

本節(jié)目標(biāo):自定義注解實(shí)現(xiàn)Mybatis插入數(shù)據(jù)操作庶橱。
本節(jié)要求:細(xì)心觀察使用自定義注解的步驟贮勃。

Step 1 :聲明自定義注解贪惹。

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

Step 2 : 在規(guī)定的注解使用范圍內(nèi)使用我們的注解

public interface UserMapper {

    @Insert("insert into user (name,password) values (?,?)")
    public void addUser(String name,String password);

}

Step 3 : 通過(guò)method.getAnnotation(Insert.class).value()使用反射解析自定義注解,得到其中的sql語(yǔ)句

//檢查是否被@Insert注解修飾
if (method.isAnnotationPresent(Insert.class)) {
    //檢查sql語(yǔ)句是否合法
    //method.getAnnotation(Insert.class).value()取得@Insert注解value中的Sql語(yǔ)句
    sql = checkSql(method.getAnnotation(Insert.class).value(),
        Insert.class.getSimpleName());
    //具體的插入數(shù)據(jù)庫(kù)操作
    insert(sql, parameters);

}

Step 4 : 根據(jù)實(shí)際場(chǎng)景調(diào)用Step 3的方法

UserMapper mapper = MethodProxyFactory.getBean(UserMapper.class);
mapper.addUser("walidake","665908");

運(yùn)行結(jié)果:

mybatis.png

以上節(jié)選自annotation中Mybatis部分寂嘉。具體CRUD操作請(qǐng)看源碼奏瞬。

總結(jié)一下從上面學(xué)到的東西:
1.聲明自定義注解,并限制適用范圍(因?yàn)槟J(rèn)是通用)
2.規(guī)定范圍內(nèi)使用注解
3.isAnnotationPresent(Insert.class)檢查注解泉孩,getAnnotation(Insert.class).value()取得注解內(nèi)容
4.根據(jù)實(shí)際場(chǎng)景應(yīng)用

Hibernate 自定義注解

本節(jié)目標(biāo):自定義注解使實(shí)體自動(dòng)建表(即生成建表SQL語(yǔ)句)
本節(jié)要求:動(dòng)手操作硼端,把未給全的代碼補(bǔ)齊。
本節(jié)規(guī)劃:仿照Hibernate寓搬,我們大概會(huì)需要@Table珍昨,@Column,還有id,我們這里暫且聲明為@PrimaryKey

仿照自定義Mybatis注解的步驟:

/**
 * 可根據(jù)需要自行定制功能
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
    String name() default "";

}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    // 列名 默認(rèn)為""
    String name() default "";

    // 長(zhǎng)度 默認(rèn)為255
    int length() default 255;

    // 是否為varchar 默認(rèn)為true
    boolean varchar() default true;

    // 是否為空 默認(rèn)可為空
    boolean isNull() default true;
}

/**
 * 有需要可以拆分成更小粒度
 * @author walidake
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PrimaryKey {
    String name() default "";
}

完成Step 1镣典,接下來(lái)是Step 2兔毙。

@Table
public class Person {
    @PrimaryKey
    private int id;

    @Column(isNull = false, length = 20)
    private String username;
    ...
}

Step 3,新建一個(gè)叫做SqlUtil的類兄春,使用Class(實(shí)體類).isAnnotationPresent(Table.class)取到@Table注解的內(nèi)容澎剥。

而我們?nèi)绾稳〉紷Column和@PrimaryKey的內(nèi)容?
使用反射赶舆,我們可以很容易做到哑姚。

// 反射取得所有Field
Field[] fields = clazz.getDeclaredFields();
...
...
// 獲取注解對(duì)象
column = fields[i].getAnnotation(Column.class);
// 設(shè)置訪問(wèn)私有變量
fields[i].setAccessible(true);
// 取得@Column的內(nèi)容
columnName = "".equals(column.name()) ? fields[i].getName(): column.name();

反射的內(nèi)容后面再寫(xiě)。(感覺(jué)每一篇都給自己挖了很多坑后面去填)

Step 4套入使用場(chǎng)景

String createSql = SqlUtil.createTable(clazz);
...
connection.createStatement().execute(createSql);

運(yùn)行結(jié)果:

hibernate.png

運(yùn)行結(jié)果正確芜茵!

自此我們完成了實(shí)戰(zhàn)模塊的內(nèi)容叙量。當(dāng)然關(guān)于Hibernate的CRUD也可以用同樣的方法做到,更進(jìn)一步還可以把二級(jí)緩存整合進(jìn)來(lái)九串,實(shí)現(xiàn)自己的一個(gè)微型框架宛乃。盡管現(xiàn)有的框架已經(jīng)很成熟了,但自己實(shí)現(xiàn)一遍還是能收獲很多東西蒸辆。

可以看出來(lái)征炼,注解簡(jiǎn)化了我們的配置。每次使用注解只需要@注解名就可以了躬贡,就跟吃春藥一樣“爽”谆奥。不過(guò)由于使用了反射,后勁太“猛”,jvm無(wú)法對(duì)代碼優(yōu)化拂玻,影響了性能酸些。這一點(diǎn)最后也會(huì)提及。

另外提一點(diǎn)檐蚜,之前想格式化hibernate生成的SQL魄懂,做大量搜索后被告知“Hibernate 使用的是開(kāi)源的語(yǔ)法解析工具 Antlr,需要進(jìn)行 SQL 語(yǔ)法解析闯第,將 SQL 語(yǔ)句整理成語(yǔ)法樹(shù)”市栗。也算一個(gè)坑吧~
不過(guò)后來(lái)找到一個(gè)除了建表SQL以外的格式化工具類,覺(jué)得還不錯(cuò)就也分享了咳短√蠲保可以在源碼中找到。

最后說(shuō)點(diǎn)什么
可以發(fā)現(xiàn)我們使用運(yùn)行時(shí)注解來(lái)搭建我們的袖珍版ORM框架咙好,因?yàn)檫\(yùn)行時(shí)注解來(lái)搭建框架相對(duì)容易而且適用性也比較廣篡腌,搭建的框架使用起來(lái)也比較簡(jiǎn)單。但在此基礎(chǔ)上因?yàn)樾枰玫椒瓷涔葱В湫市阅芟鄬?duì)不高嘹悼。因此叛甫,多數(shù)Web應(yīng)用使用運(yùn)行時(shí)注解,而像Android等對(duì)效率性能要求較高的平臺(tái)一般使用源碼級(jí)別注解來(lái)搭建杨伙。下一節(jié)我們討論怎么玩一玩源碼級(jí)注解合溺。

源代碼地址: https://github.com/walidake/Annotation

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缀台,隨后出現(xiàn)的幾起案子棠赛,更是在濱河造成了極大的恐慌,老刑警劉巖膛腐,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睛约,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哲身,警方通過(guò)查閱死者的電腦和手機(jī)辩涝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)勘天,“玉大人怔揩,你說(shuō)我怎么就攤上這事「浚” “怎么了商膊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宠进。 經(jīng)常有香客問(wèn)我晕拆,道長(zhǎng),這世上最難降的妖魔是什么材蹬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任实幕,我火速辦了婚禮,結(jié)果婚禮上堤器,老公的妹妹穿的比我還像新娘昆庇。我一直安慰自己,他們只是感情好闸溃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布整吆。 她就那樣靜靜地躺著,像睡著了一般圈暗。 火紅的嫁衣襯著肌膚如雪掂为。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天员串,我揣著相機(jī)與錄音,去河邊找鬼昼扛。 笑死寸齐,一個(gè)胖子當(dāng)著我的面吹牛欲诺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渺鹦,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼扰法,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了毅厚?” 一聲冷哼從身側(cè)響起塞颁,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吸耿,沒(méi)想到半個(gè)月后祠锣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咽安,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年伴网,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妆棒。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澡腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糕珊,到底是詐尸還是另有隱情动分,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布红选,位于F島的核電站刺啦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏纠脾。R本人自食惡果不足惜玛瘸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苟蹈。 院中可真熱鬧糊渊,春花似錦、人聲如沸慧脱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)菱鸥。三九已至宗兼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氮采,已是汗流浹背殷绍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹊漠,地道東北人主到。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓茶行,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親登钥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子畔师,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來(lái) What:A...
    zlcook閱讀 29,131評(píng)論 15 116
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,837評(píng)論 25 707
  • 我很好,簡(jiǎn)單的三個(gè)字牧牢,蘊(yùn)含了復(fù)雜的情緒看锉。 往往這句話的前一句會(huì)是,“你還好嗎塔鳍?” 如果我真的很好伯铣, 我會(huì)回答為什么...
    大引閱讀 377評(píng)論 0 0