版權(quán)申明】非商業(yè)目的可自由轉(zhuǎn)載
博文地址:https://blog.csdn.net/shusheng0007/article/details/80622035
出自:shusheng007
概述
照例先啰嗦幾句,剛開始接觸Java的時候,某一天發(fā)現(xiàn)調(diào)用的一個方法被劃了一個刪除橫線,查看這個方法的源代碼的時候發(fā)現(xiàn)除了上面有一句@Deprecated
代碼外,和其他方法沒有區(qū)別,所以我斷定就是這貨起的作用,當(dāng)時覺得好神奇,于是乎我開始了對Java注解的了解,這個過程是不連續(xù)的,最近比較閑,所以總結(jié)一下。
理解Java注解
注解就相當(dāng)于對源代碼打的標(biāo)簽,給代碼打上標(biāo)簽和刪除標(biāo)簽對源代碼沒有任何影響。有的人要說了,你盡幾把瞎扯,沒有影響,打這些標(biāo)簽干毛線呢?其實不是這些標(biāo)簽自己起了什么作用,而且外部工具通過訪問這些標(biāo)簽,然后根據(jù)不同的標(biāo)簽做出了相應(yīng)的處理。這是注解的精髓,理解了這一點一切就變得不再那么神秘。
例如我們寫代碼用的IDE(例如 IntelliJ Idea),它檢查發(fā)現(xiàn)某一個方法上面有@Deprecated
這個注解,它就會在所有調(diào)用這個方法的地方將這個方法標(biāo)記為刪除。
訪問和處理Annotation的工具統(tǒng)稱為APT(Annotation Processing Tool)
基本語法
注解可以分為以下3類
基本注解
Java內(nèi)置的注解共有5個
@Override
:讓編譯器檢查被標(biāo)記的方法,保證其重寫了父類的某一個方法。此注解只能標(biāo)記方法。源碼如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- 1
- 2
- 3
- 4
@Deprecated
:標(biāo)記某些程序元素已經(jīng)過時,程序員請不要再使用了。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
- 1
- 2
- 3
- 4
- 5
@SuppressWarnings
:告訴編譯器不要給老子顯示警告,老子不想看,老子清楚的知道自己在干什么。源碼如下:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
- 1
- 2
- 3
- 4
- 5
其內(nèi)部有一個String數(shù)組,根據(jù)傳入的值來取消相應(yīng)的警告:
deprecation:使用了不贊成使用的類或方法時的警告;
unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時的警告,例如當(dāng)使用集合時沒有用泛型 (Generics) 來指定集合保存的類型;
fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時的警告;
path:在類路徑、源文件路徑等中有不存在的路徑時的警告;
serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時的警告;
finally:任何 finally 子句不能正常完成時的警告;
all:關(guān)于以上所有情況的警告。
@SafeVarargs
(Java7 新增) :@SuppressWarnings
可以用在各種需要取消警告的地方,而 @SafeVarargs
主要用在取消參數(shù)的警告。就是說編譯器如果檢查到你對方法參數(shù)的操作,有可能發(fā)生問題時會給出警告,但是你很自(任)性,老子不要警告,于是你就加上了這個標(biāo)簽。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
- 1
- 2
- 3
- 4
其實這個注解是專為取消堆污染警告設(shè)置的,因為Java7會對可能產(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!
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果不使用 @SafeVarargs
,這個方法在編譯時候是會產(chǎn)生警告的 : “…使用了未經(jīng)檢查或不安全的操作?!?/strong>,用了就不會有警告,但是在運(yùn)行時會拋異常。
@FunctionalInterface
(Java8 新增): 標(biāo)記型注解,告訴編譯器檢查被標(biāo)注的接口是否是一個函數(shù)接口,即檢查這個接口是否只包含一個抽象方法,只有函數(shù)接口才可以使用Lambda
表達(dá)式創(chuàng)建實例。源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
- 1
- 2
- 3
- 4
元注解
用來給其他注解打標(biāo)簽的注解,即用來注解其他注解的注解。元注解共有6個。從上面的基本注解的源代碼中就會看到使用了元注解來注解自己。
@Retention:用于指定被此元注解標(biāo)注的注解的保留時長,源代碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
- 1
- 2
- 3
- 4
- 5
- 6
從源代碼中可以看出,其有一個屬性value
,返回一個枚舉RetentionPolicy
類型,有3種類型:
- RetentionPolicy.SOURCE: :注解信息只保留在源代碼中,編譯器編譯源碼時會將其直接丟棄。
- RetentionPolicy.CLASS::注解信息保留在
class
文件中,但是虛擬機(jī)VM
不會維護(hù)默認(rèn)值。 - RetentionPolicy.RUNTIME::注解信息保留在
class
文件中,而且VM
也會持有此注解信息,所以可以通過反射的方式獲得注解信息。
@Target:用于指定被此元注解標(biāo)注的注解可以標(biāo)注的程序元素,源碼如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
- 1
- 2
- 3
- 4
- 5
- 6
從源碼中可以看出,其有一個屬性value
,返回一個枚舉ElementType
類型的數(shù)組,這個數(shù)組的值就代表了可以使用的程序元素。
public enum ElementType {
/**標(biāo)明該注解可以用于類、接口(包括注解類型)或enum聲明*/
TYPE,
/** 標(biāo)明該注解可以用于字段(域)聲明,包括enum實例 */
FIELD,
/** 標(biāo)明該注解可以用于方法聲明 */
METHOD,
/** 標(biāo)明該注解可以用于參數(shù)聲明 */
PARAMETER,
/** 標(biāo)明注解可以用于構(gòu)造函數(shù)聲明 */
CONSTRUCTOR,
/** 標(biāo)明注解可以用于局部變量聲明 */
LOCAL_VARIABLE,
/** 標(biāo)明注解可以用于注解聲明(應(yīng)用于另一個注解上)*/
ANNOTATION_TYPE,
/** 標(biāo)明注解可以用于包聲明 */
PACKAGE,
/**
* 標(biāo)明注解可以用于類型參數(shù)聲明(1.8新加入)
*/
TYPE_PARAMETER,
/**
* 類型使用聲明(1.8新加入)
*/
TYPE_USE
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
例如@Override
注解使用了 @Target(ElementType.METHOD)
,那么就意味著,它只能注解方法,不能注解其他程序元素。
當(dāng)注解未指定Target值時,則此注解可以用于任何元素之上,多個值使用{}包含并用逗號隔開,下面代碼表示,此Annotation
既可以注解構(gòu)造函數(shù)、字段和方法:
@Target(value={CONSTRUCTOR, FIELD, METHOD})
- 1
值得注意的是,TYPE_PARAMETER
,TYPE_USE
是Java8 加入的新類型,在Java8之前,只能在聲明各種程序元素時使用注解,而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檢查,注意這些注解中用于外部工具,它們不會對類型轉(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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
雖然Java8 提供了類型注解,但是沒有提供APT
,所以需要框架自己實現(xiàn)。
@Documented:將被標(biāo)注的注解生成到javadoc
中。
@Inherited:其讓被修飾的注解擁有被繼承的能力。如下,我們有一個用@Inherited
修飾的注解@InAnnotation
,那么這個注解就擁有了被繼承的能力。
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InAnnotation{
}
@InAnnotation
class Base{}
class Son extends Base{}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
當(dāng)使用此注解修飾一個基類Base
, 其子類Son
并沒有使用任何注解修飾,但是其已經(jīng)擁有了@InAnnotation
這個注解,相當(dāng)于Son
已經(jīng)被@InAnnotation
修飾了
@Repeatable :使被修飾的注解可以重復(fù)的注解某一個程序元素。例如下面的代碼中@ShuSheng
這個自定義注解使用了@Repeatable
修飾,所以其可以按照下面的語法重復(fù)的注解一個類。
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo{}
- 1
- 2
- 3
如何定義一個重復(fù)注解呢,如下所示,我們需要先定義一個容器,例如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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
自定義注解
通過前面的講解,很容易得出如何自定義一個注解。注解是以關(guān)鍵字@interface
來定義的,下面我們自定義一個注解。
注解按照有無成員變量可以分為:
標(biāo)記Annotation:無成員變量,只利用自身是否存在來提供信息。
@Target(ElementType.METHOD)//只能應(yīng)用于方法上。 @Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時 public @interface Test { }
- 1
- 2
- 3
- 4
元數(shù)據(jù)Annotation:有一個或者多個成員變量,可以接收外界信息。
@Target(ElementType.TYPE)//只能應(yīng)用于類型上,包括類,接口。 @Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時 public @interface Table { String name() default ""; }
- 1
- 2
- 3
- 4
- 5
以上就是我們定義的兩種注解,那么如何使用呢
//在類上使用該注解
@Table (name = "MEMBER")
public class Member {
@Test
public void method()
{...}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如何使用注解
就像我們文章開頭說的,當(dāng)我們使用注解修飾了程序元素后,這種Annotation不會自己起作用,的需要APT
的幫助,那么這些APT
就需要讀取代碼中的屬性信息,那么如何讀取呢?答案是通過反射!
Annotation
接口是所有注解的父接口(需要通過反編譯查看),在java.lang.reflect
反射包下存在一個叫AnnotatedElement
接口,其表示程序中可以接受注解的程序元素,例如 類,方法,字段,構(gòu)造函數(shù),包等等。而Java為使用反射的主要類實現(xiàn)了此接口,如反射包內(nèi)的Constructor類、Field類、Method類、Package類和Class類。
當(dāng)我們通過反射技術(shù)獲取到反射包內(nèi)的那些類型的實例后,就可以使用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ù)注解功能,因此需要此方法獲取修飾該程序元素的指定類型的多個Annotation
獲取注解簡單示例
首先我們定義了兩個注解@Master
與@ShuSheng
,@ShuSheng
是一個可重復(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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
然后我們定義了兩個類,使用定義好的注解來修飾,如下
@Master
public class AnoBase {
}
@ShuSheng(name="frank",age=18)
@ShuSheng(age = 20)
public class AnnotationDemo extends AnoBase{
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
最后我們來調(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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
執(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
- 1
- 2
- 3
自定義注解處理器(APT)
了解完注解與反射的相關(guān)API后,就可以更進(jìn)一步。下面的實例自定義了一個APT
,完成通過注解構(gòu)建SQL
語句的功能。此處代碼來自此處。下面代碼要求對數(shù)據(jù)庫有初步認(rèn)識。
先定義相關(guān)的注解
/**
* 用來注解表
*/
@Target(ElementType.TYPE)//只能應(yīng)用于類上
@Retention(RetentionPolicy.RUNTIME)//保存到運(yùn)行時
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)實例類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;//個人描述
//省略set get.....
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
上述定義4個注解,分別是@DBTable(用于類上)、@Constraints(用于字段上)、 @SQLString(用于字段上)、@SQLString(用于字段上)并在Member類中使用這些注解,這些注解的作用的是用于幫助注解處理器生成創(chuàng)建數(shù)據(jù)庫表MEMBER的構(gòu)建語句,在這里有點需要注意的是,我們使用了嵌套注解@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));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
輸出結(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)
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
常用場景
Annotation,特別是自定義注解,一般是在構(gòu)建框架或者通用庫時候使用的較多。下面列出了些我知道的,其他的歡迎補(bǔ)充。
Spring框架:解耦神器。
JUnit :測試框架
ButterKnife :在Android中使用的視圖注解框架,Android的小伙伴們都知道。
Dagger2 :依賴注入框架,在Android中用的也比較多。
Retrofit :Http網(wǎng)絡(luò)訪問框架,Android網(wǎng)絡(luò)請求標(biāo)配。
Room :Google 發(fā)布的用于Android開發(fā)的本地數(shù)據(jù)庫解決方案庫。
參考文章:深入理解Java注解類型(@Annotation)
《瘋狂Java講義》
《Think in java》