版權(quán)申明】非商業(yè)目的附文章鏈接可自由轉(zhuǎn)載
博文地址:http://www.reibang.com/p/9e72ca22fc50
出自:shusheng007
概述
照例先啰嗦幾句挖腰,剛開始接觸Java的時(shí)候,某一天發(fā)現(xiàn)調(diào)用的一個(gè)方法被劃了一個(gè)刪除橫線,查看這個(gè)方法的源代碼的時(shí)候發(fā)現(xiàn)除了上面有一句@Deprecated
代碼外,和其他方法沒有區(qū)別,所以我斷定就是這貨起的作用克懊,當(dāng)時(shí)覺得好神奇,于是乎我開始了對Java注解的了解,這個(gè)過程是不連續(xù)的吊圾,最近比較閑,所以總結(jié)一下翰蠢。
理解Java注解
注解就相當(dāng)于對源代碼打的標(biāo)簽项乒,給代碼打上標(biāo)簽和刪除標(biāo)簽對源代碼沒有任何影響。有的人要說了梁沧,你盡幾把瞎扯檀何,沒有影響,打這些標(biāo)簽干毛線呢廷支?其實(shí)不是這些標(biāo)簽自己起了什么作用频鉴,而且外部工具通過訪問這些標(biāo)簽,然后根據(jù)不同的標(biāo)簽做出了相應(yīng)的處理恋拍。這是注解的精髓垛孔,理解了這一點(diǎn)一切就變得不再那么神秘。
例如我們寫代碼用的IDE(例如 IntelliJ Idea),它檢查發(fā)現(xiàn)某一個(gè)方法上面有@Deprecated
這個(gè)注解芝囤,它就會(huì)在所有調(diào)用這個(gè)方法的地方將這個(gè)方法標(biāo)記為刪除似炎。
訪問和處理Annotation的工具統(tǒng)稱為APT(Annotation Processing Tool)
基本語法
注解可以分為以下3類
基本注解
Java內(nèi)置的注解共有5個(gè)
@Override
:讓編譯器檢查被標(biāo)記的方法,保證其重寫了父類的某一個(gè)方法悯姊。此注解只能標(biāo)記方法羡藐。源碼如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Deprecated
:標(biāo)記某些程序元素已經(jīng)過時(shí),程序員請不要再使用了悯许。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
@SuppressWarnings
:告訴編譯器不要給老子顯示警告仆嗦,老子不想看,老子清楚的知道自己在干什么先壕。源碼如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
其內(nèi)部有一個(gè)String數(shù)組瘩扼,根據(jù)傳入的值來取消相應(yīng)的警告:
deprecation:使用了不贊成使用的類或方法時(shí)的警告谆甜;
unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型;
fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告;
path:在類路徑集绰、源文件路徑等中有不存在的路徑時(shí)的警告;
serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告;
finally:任何 finally 子句不能正常完成時(shí)的警告;
all:關(guān)于以上所有情況的警告规辱。
@SafeVarargs
(Java7 新增) :@SuppressWarnings
可以用在各種需要取消警告的地方,而 @SafeVarargs
主要用在取消參數(shù)的警告栽燕。就是說編譯器如果檢查到你對方法參數(shù)的操作罕袋,有可能發(fā)生問題時(shí)會(huì)給出警告,但是你很自(任)性碍岔,老子不要警告浴讯,于是你就加上了這個(gè)標(biāo)簽。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
其實(shí)這個(gè)注解是專為取消堆污染警告設(shè)置的蔼啦,因?yàn)镴ava7會(huì)對可能產(chǎn)生堆污染的代碼提出警告榆纽,什么是堆污染?且看下面代碼
@SafeVarargs
private static void method(List<String>... strLists) {
List[] array = strLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; //非法操作捏肢,但是沒有警告
String s = strLists[0].get(0); //ClassCastException at runtime!
}
如果不使用 @SafeVarargs
奈籽,這個(gè)方法在編譯時(shí)候是會(huì)產(chǎn)生警告的 : “...使用了未經(jīng)檢查或不安全的操作⊥液眨”,用了就不會(huì)有警告唠摹,但是在運(yùn)行時(shí)會(huì)拋異常。
@FunctionalInterface
(Java8 新增): 標(biāo)記型注解奉瘤,告訴編譯器檢查被標(biāo)注的接口是否是一個(gè)函數(shù)接口勾拉,即檢查這個(gè)接口是否只包含一個(gè)抽象方法,只有函數(shù)接口才可以使用Lambda
表達(dá)式創(chuàng)建實(shí)例盗温。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
元注解
用來給其他注解打標(biāo)簽的注解藕赞,即用來注解其他注解的注解。元注解共有6個(gè)卖局。從上面的基本注解的源代碼中就會(huì)看到使用了元注解來注解自己斧蜕。
@Retention:用于指定被此元注解標(biāo)注的注解的保留時(shí)長,源代碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
從源代碼中可以看出砚偶,其有一個(gè)屬性value
,返回一個(gè)枚舉RetentionPolicy
類型批销,有3種類型:
- RetentionPolicy.SOURCE: :注解信息只保留在源代碼中,編譯器編譯源碼時(shí)會(huì)將其直接丟棄染坯。
-
RetentionPolicy.CLASS::注解信息保留在
class
文件中均芽,但是虛擬機(jī)VM
不會(huì)維護(hù)默認(rèn)值。 -
RetentionPolicy.RUNTIME::注解信息保留在
class
文件中单鹿,而且VM
也會(huì)持有此注解信息掀宋,所以可以通過反射的方式獲得注解信息。
@Target:用于指定被此元注解標(biāo)注的注解可以標(biāo)注的程序元素,源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
從源碼中可以看出劲妙,其有一個(gè)屬性value
,返回一個(gè)枚舉ElementType
類型的數(shù)組湃鹊,這個(gè)數(shù)組的值就代表了可以使用的程序元素。
public enum ElementType {
/**標(biāo)明該注解可以用于類镣奋、接口(包括注解類型)或enum聲明*/
TYPE,
/** 標(biāo)明該注解可以用于字段(域)聲明币呵,包括enum實(shí)例 */
FIELD,
/** 標(biāo)明該注解可以用于方法聲明 */
METHOD,
/** 標(biāo)明該注解可以用于參數(shù)聲明 */
PARAMETER,
/** 標(biāo)明注解可以用于構(gòu)造函數(shù)聲明 */
CONSTRUCTOR,
/** 標(biāo)明注解可以用于局部變量聲明 */
LOCAL_VARIABLE,
/** 標(biāo)明注解可以用于注解聲明(應(yīng)用于另一個(gè)注解上)*/
ANNOTATION_TYPE,
/** 標(biāo)明注解可以用于包聲明 */
PACKAGE,
/**
* 標(biāo)明注解可以用于類型參數(shù)聲明(1.8新加入)
*/
TYPE_PARAMETER,
/**
* 類型使用聲明(1.8新加入)
*/
TYPE_USE
}
例如@Override
注解使用了 @Target(ElementType.METHOD)
,那么就意味著侨颈,它只能注解方法富雅,不能注解其他程序元素。
當(dāng)注解未指定Target值時(shí)肛搬,則此注解可以用于任何元素之上,多個(gè)值使用{}包含并用逗號隔開毕贼,下面代碼表示温赔,此Annotation
既可以注解構(gòu)造函數(shù)、字段和方法:
@Target(value={CONSTRUCTOR, FIELD, METHOD})
值得注意的是鬼癣,TYPE_PARAMETER
陶贼,TYPE_USE
是Java8 加入的新類型,在Java8之前待秃,只能在聲明各種程序元素時(shí)使用注解拜秧,而TYPE_PARAMETER
允許使用注解修飾參數(shù)類型,TYPE_USE
允許使用注解修飾任意類型章郁。
//TYPE_PARAMETER 修飾類型參數(shù)
class A<@Parameter T> { }
//TYPE_USE則可以用于標(biāo)注任意類型(不包括class)
//用于父類或者接口
class Image implements @Rectangular Shape { }
//用于構(gòu)造函數(shù)
new @Path String("/usr/bin")
//用于強(qiáng)制轉(zhuǎn)換和instanceof檢查,注意這些注解中用于外部工具枉氮,它們不會(huì)對類型轉(zhuǎn)換或者instanceof的檢查行為帶來任何影響。
String path=(@Path String)input;
if(input instanceof @Path String)
//用于指定異常
public Person read() throws @Localized IOException.
//用于通配符綁定
List<@ReadOnly ? extends Person>
List<? extends @ReadOnly Person>
@NotNull String.class //非法暖庄,不能標(biāo)注class
import java.lang.@NotNull String //非法聊替,不能標(biāo)注import
雖然Java8 提供了類型注解,但是沒有提供APT
,所以需要框架自己實(shí)現(xiàn)培廓。
@Documented:將被標(biāo)注的注解生成到javadoc
中惹悄。
@Inherited:其讓被修飾的注解擁有被繼承的能力。如下肩钠,我們有一個(gè)用@Inherited
修飾的注解@InAnnotation
泣港,那么這個(gè)注解就擁有了被繼承的能力。
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InAnnotation{
}
@InAnnotation
class Base{}
class Son extends Base{}
當(dāng)使用此注解修飾一個(gè)基類Base
, 其子類Son
并沒有使用任何注解修飾价匠,但是其已經(jīng)擁有了@InAnnotation
這個(gè)注解当纱,相當(dāng)于Son
已經(jīng)被@InAnnotation
修飾了
@Repeatable :使被修飾的注解可以重復(fù)的注解某一個(gè)程序元素。例如下面的代碼中@ShuSheng
這個(gè)自定義注解使用了@Repeatable
修飾踩窖,所以其可以按照下面的語法重復(fù)的注解一個(gè)類惫东。
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo{}
如何定義一個(gè)重復(fù)注解呢,如下所示,我們需要先定義一個(gè)容器,例如ShuShengs
廉沮,然后將其作為參數(shù)傳入@Repeatable
中颓遏。
@Repeatable(ShuShengs.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuSheng {
String name() default "ben";
int age();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuShengs {
ShuSheng[] value();
}
自定義注解
通過前面的講解,很容易得出如何自定義一個(gè)注解滞时。注解是以關(guān)鍵字@interface
來定義的叁幢,下面我們自定義一個(gè)注解。
注解按照有無成員變量可以分為:
-
標(biāo)記Annotation:無成員變量坪稽,只利用自身是否存在來提供信息曼玩。
@Target(ElementType.METHOD)//只能應(yīng)用于方法上。 @Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時(shí) public @interface Test { }
元數(shù)據(jù)Annotation:有一個(gè)或者多個(gè)成員變量窒百,可以接收外界信息黍判。
```
@Target(ElementType.TYPE)//只能應(yīng)用于類型上,包括類篙梢,接口顷帖。
@Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時(shí)
public @interface Table {
String name() default "";
}
```
以上就是我們定義的兩種注解,那么如何使用呢
//在類上使用該注解
@Table (name = "MEMBER")
public class Member {
@Test
public void method()
{...}
}
如何使用注解
就像我們文章開頭說的渤滞,當(dāng)我們使用注解修飾了程序元素后贬墩,這種Annotation不會(huì)自己起作用,的需要APT
的幫助妄呕,那么這些APT
就需要讀取代碼中的屬性信息陶舞,那么如何讀取呢?答案是通過反射绪励!
Annotation
接口是所有注解的父接口(需要通過反編譯查看)肿孵,在java.lang.reflect
反射包下存在一個(gè)叫AnnotatedElement
接口,其表示程序中可以接受注解的程序元素疏魏,例如 類颁井,方法,字段蠢护,構(gòu)造函數(shù)雅宾,包等等。而Java為使用反射的主要類實(shí)現(xiàn)了此接口葵硕,如反射包內(nèi)的Constructor類眉抬、Field類、Method類懈凹、Package類和Class類蜀变。
當(dāng)我們通過反射技術(shù)獲取到反射包內(nèi)的那些類型的實(shí)例后,就可以使用AnnotatedElement
接口的中的API方法來獲取注解的信息了介评。
-
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
: 返回該元素上存在的指定類型的注解库北,如果不存在則返回 null爬舰。 -
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass){}
:返回該元素上存在的直接修飾該元素的指定類型的注解,如果不存在則返回null. -
Annotation[] getAnnotations();
:返回該元素上存在的所有注解寒瓦。 -
Annotation[] getDeclaredAnnotations();
:返回該元素上存在的直接修飾該元素的所有注解情屹。 -
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass){}
:該方法功能與前面getAnnotation
方法類似,但是由于Java8 加入了重復(fù)注解功能杂腰,因此需要此方法獲取修飾該程序元素的指定類型的多個(gè)Annotation
獲取注解簡單示例
首先我們定義了兩個(gè)注解@Master
與@ShuSheng
垃你,@ShuSheng
是一個(gè)可重復(fù)注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Master {
}
@Repeatable(ShuShengs.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuSheng {
String name() default "ben";
int age();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ShuShengs {
ShuSheng[] value();
}
然后我們定義了兩個(gè)類,使用定義好的注解來修飾喂很,如下
@Master
public class AnoBase {
}
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo extends AnoBase{
}
最后我們來調(diào)用相關(guān)函數(shù)獲取相應(yīng)的結(jié)果
private static void getAnnotation()
{
Class<?> cInstance=AnnotationDemo.class;
//獲取AnnotationDemo上的重復(fù)注解
ShuSheng[] ssAons= cInstance.getAnnotationsByType(ShuSheng.class);
System.out.println("重復(fù)注解:"+Arrays.asList(ssAons).toString());
//獲取AnnotationDemo上的所有注解惜颇,包括從父類繼承的
Annotation[] allAno=cInstance.getAnnotations();
System.out.println("所有注解:"+Arrays.asList(allAno).toString());
//判斷AnnotationDemo上是否存在Master注解
boolean isP=cInstance.isAnnotationPresent(Master.class);
System.out.println("是否存在Master: "+isP);
}
執(zhí)行結(jié)果如下:
重復(fù)注解:[@top.ss007.ShuSheng(name=frank, age=18), @top.ss007.ShuSheng(name=ben, age=20)]
所有注解:[@top.ss007.ShuShengs(value=[@top.ss007.ShuSheng(name=frank, age=18), @top.ss007.ShuSheng(name=ben, age=20)])]
是否存在Master: false
自定義注解處理器(APT)
了解完注解與反射的相關(guān)API后,就可以更進(jìn)一步少辣。下面的實(shí)例自定義了一個(gè)APT
,完成通過注解構(gòu)建SQL
語句的功能凌摄。此處代碼來自此處。下面代碼要求對數(shù)據(jù)庫有初步認(rèn)識漓帅。
先定義相關(guān)的注解
/**
* 用來注解表
*/
@Target(ElementType.TYPE)//只能應(yīng)用于類上
@Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時(shí)
public @interface DBTable {
String name() default "";
}
/**
* 注解Integer類型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
//該字段對應(yīng)數(shù)據(jù)庫表列名
String name() default "";
//嵌套注解
Constraints constraint() default @Constraints;
}
/**
* 注解String類型的字段
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
//對應(yīng)數(shù)據(jù)庫表的列名
String name() default "";
//列類型分配的長度锨亏,如varchar(30)的30
int value() default 0;
Constraints constraint() default @Constraints;
}
/**
* 約束注解
*/
@Target(ElementType.FIELD)//只能應(yīng)用在字段上
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
//判斷是否作為主鍵約束
boolean primaryKey() default false;
//判斷是否允許為null
boolean allowNull() default false;
//判斷是否唯一
boolean unique() default false;
}
/**
* 數(shù)據(jù)庫表Member對應(yīng)實(shí)例類bean
*/
@DBTable(name = "MEMBER")
public class Member {
//主鍵ID
@SQLString(name = "ID",value = 50, constraint = @Constraints(primaryKey = true))
private String 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;//個(gè)人描述
//省略set get.....
}
上述定義4個(gè)注解,分別是@DBTable(用于類上)煎殷、@Constraints(用于字段上)、 @SQLString(用于字段上)腿箩、@SQLString(用于字段上)并在Member類中使用這些注解豪直,這些注解的作用的是用于幫助注解處理器生成創(chuàng)建數(shù)據(jù)庫表MEMBER的構(gòu)建語句,在這里有點(diǎn)需要注意的是珠移,我們使用了嵌套注解@Constraints弓乙,該注解主要用于判斷字段是否為null或者字段是否唯一。接下來就需要編寫我們自己的注解處理器了钧惧。
public class TableCreator {
public static String createTableSql(String className) throws ClassNotFoundException {
Class<?> cl = Class.forName(className);
DBTable dbTable = cl.getAnnotation(DBTable.class);
//如果沒有表注解暇韧,直接返回
if(dbTable == null) {
System.out.println(
"No DBTable annotations in class " + className);
return null;
}
String tableName = dbTable.name();
// If the name is empty, use the Class name:
if(tableName.length() < 1)
tableName = cl.getName().toUpperCase();
List<String> columnDefs = new ArrayList<String>();
//通過Class類API獲取到所有成員字段
for(Field field : cl.getDeclaredFields()) {
String columnName = null;
//獲取字段上的注解
Annotation[] anns = field.getDeclaredAnnotations();
if(anns.length < 1)
continue; // Not a db table column
//判斷注解類型
if(anns[0] instanceof SQLInteger) {
SQLInteger sInt = (SQLInteger) anns[0];
//獲取字段對應(yīng)列名稱,如果沒有就是使用字段名稱替代
if(sInt.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sInt.name();
//構(gòu)建語句
columnDefs.add(columnName + " INT" +
getConstraints(sInt.constraint()));
}
//判斷String類型
if(anns[0] instanceof SQLString) {
SQLString sString = (SQLString) anns[0];
// Use field name if name not specified.
if(sString.name().length() < 1)
columnName = field.getName().toUpperCase();
else
columnName = sString.name();
columnDefs.add(columnName + " VARCHAR(" +
sString.value() + ")" +
getConstraints(sString.constraint()));
}
}
//數(shù)據(jù)庫表構(gòu)建語句
StringBuilder createCommand = new StringBuilder(
"CREATE TABLE " + tableName + "(");
for(String columnDef : columnDefs)
createCommand.append("\n " + columnDef + ",");
// Remove trailing comma
String tableCreate = createCommand.substring(
0, createCommand.length() - 1) + ");";
return tableCreate;
}
/**
* 判斷該字段是否有其他約束
* @param con
* @return
*/
private static String getConstraints(Constraints con) {
String constraints = "";
if(!con.allowNull())
constraints += " NOT NULL";
if(con.primaryKey())
constraints += " PRIMARY KEY";
if(con.unique())
constraints += " UNIQUE";
return constraints;
}
public static void main(String[] args) throws Exception {
String[] arg={"com.zejian.annotationdemo.Member"};
for(String className : arg) {
System.out.println("Table Creation SQL for " +
className + " is :\n" + createTableSql(className));
}
}
}
輸出結(jié)果為:
Table Creation SQL for com.zejian.annotationdemo.Member is :
CREATE TABLE MEMBER(
ID VARCHAR(50) NOT NULL PRIMARY KEY,
NAME VARCHAR(30) NOT NULL,
AGE INT NOT NULL,
DESCRIPTION VARCHAR(150)
);
常用場景
Annotation浓瞪,特別是自定義注解懈玻,一般是在構(gòu)建框架或者通用庫時(shí)候使用的較多。下面列出了些我知道的乾颁,其他的歡迎補(bǔ)充涂乌。
Spring框架:解耦神器。
JUnit :測試框架
ButterKnife :在Android中使用的視圖注解框架英岭,Android的小伙伴們都知道湾盒。
Dagger2 :依賴注入框架,在Android中用的也比較多诅妹。
Retrofit :Http網(wǎng)絡(luò)訪問框架罚勾,Android網(wǎng)絡(luò)請求標(biāo)配毅人。
Room :Google 發(fā)布的用于Android開發(fā)的本地?cái)?shù)據(jù)庫解決方案庫。
參考文章:深入理解Java注解類型(@Annotation)
《瘋狂Java講義》
《Think in java》