引子:全中國的Java程序員都知道,現(xiàn)在主流的項(xiàng)目架構(gòu)都是ssm、ssh、springcloud等,而這些框架都離不開spring,而spring中使用了大量的注解(包括spring自定義的注解)。因此想要會用注解,我們就得知道Java注解的原理和基本用法,這樣有助于我們在項(xiàng)目中如魚得水。
在JDK5.0中,新增了很多對現(xiàn)在影響很大的特性,如:枚舉、自動裝箱和拆箱、注解、泛型等等。其中注解的引入,是為了增加對元數(shù)據(jù)的支持。
注解:是一個(gè)接口,程序可以通過反射來獲取指定程序元素的Annotation對象,然后通過Annotation對象來取得注解里的元數(shù)據(jù),注解能用來為程序元素(包、類、方法、成員變量等)設(shè)置元數(shù)據(jù),它不影響程序代碼的執(zhí)行。如果希望讓程序中的Annotation在運(yùn)行時(shí)起一定作用,只有通過某種配套的工具對Annotation中的信息進(jìn)行訪問和處理,這個(gè)工具同城為APT(Annotation program Tool)
JDK5.0除了增加注解特性之外,還提供了5個(gè)基本的Annotation:
- @Overrride:限定重寫父類方法(旨在強(qiáng)制性提醒)
- @Deprecated:表示某個(gè)程序元素(類、方法)已過時(shí)
- @SuppressWarnings:抑制編譯警告
- @SafeVarargs:堆污染警告
- @FunctionalInterface:指定某個(gè)接口必須為函數(shù)式接口
注:函數(shù)式接口是指一個(gè)接口中只包含一個(gè)抽象方法(可以包含多個(gè)默認(rèn)方法或多個(gè)static方法)
以上這幾個(gè)注解,在我們的項(xiàng)目經(jīng)??梢?,但是因?yàn)樗麄儗Τ绦蛑皇且粋€(gè)強(qiáng)制提醒或者警告作用,并不影響程序的執(zhí)行,因?yàn)槲覀兌紱]在意這些注解的作用,而當(dāng)spring出來之后,大量的注解眼花繚亂,作用各異,但他們都有一個(gè)共同作用:
讓我們在編碼過程中簡化了不少重復(fù)性的代碼。
因此我們在了解了注解的原理之后,必須要能自定義一些注解并且使用它到項(xiàng)目中去,才能讓我們更好的了解和使用它。
而要想自定義注解,
就必須得了解Java提供的幾個(gè)元注解
那什么是元注解呢?
元注解:就是負(fù)責(zé)注解其它注解的注解
在Java5之后定義了4個(gè)標(biāo)準(zhǔn)的元注解,分別是:
1. @Target
2. @Retention
3. @Documented
4. @Inherited
@Target
target注解用來標(biāo)識注解所修飾的對象范圍。
它的可用范圍(ElementType的取值范圍)有:
- CONSTRUCTOR:用于描述構(gòu)造器(構(gòu)造方法)
- FIELD:用于描述域(成員變量)
- LOCAL_VARIABLE:用于描述局部變量(局部變量)
- METHOD:用于描述方法(普通方法)
- PACKAGE:用于描述包(包定義)
- PARAMETER:用于描述參數(shù)(如catch等參數(shù))
- TYPE:用于描述類、接口(包括注解類型) 或enum聲明
- ANNOTATION_TYPE:用于注解
使用實(shí)例:
1@Target(ElementType.FIELD)2public @interface TargetTest6{3}4@Target({ElementType.TYPE_PARAMETER,ElementType.METHOD})5public @interface TargetTest7{6}
@Retention
@Retention定義了該Annotation被保留的時(shí)間長短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取(請注意并不影響class的執(zhí)行,因?yàn)锳nnotation與class在使用上是被分離的)。使用這個(gè)meta-Annotation可以對 Annotation的“生命周期”限制。
它的取值(RetentionPoicy)有:
- SOURCE:在源文件中有效(即源文件保留),注解只保留在源代碼中,編譯器直接丟棄這種注解。
- CLASS:在class文件中有效(即class保留),編譯器把注解記錄在class文件中,當(dāng)Java程序運(yùn)行時(shí),JVM不能獲取該注解的信息。
- RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留),編譯器將把注解記錄在class文件中,當(dāng)Java運(yùn)行時(shí),JVM可以獲取注解的信息,程序可以通過反射獲取該注解的信息。
1@Target(ElementType.FIELD)2@Retention(RetentionPolicy.RUNTIME)3public @interface TargetTest6{4}
@Inherited
@Inherited注解指定被它休市的注解將具備繼承性:如果莫個(gè)類使用了@XXX注解,則其子類自動被@XXX修飾
1@Target(ElementType.FIELD)2@Retention(RetentionPolicy.RUNTIME)3@Inherited4@interface TargetTest6{5}6//所有使用了@TargetTest6注解的類講具備繼承性,7//也就是它的子類自動帶上@TargetTest6注解
@Documented
@Documented用于指定被該注解修飾的注解類將被javadoc工具提取城文檔.
1@Target(ElementType.FIELD)2@Retention(RetentionPolicy.RUNTIME)3@Inherited4@Documented5@interface TargetTest6{6}7//javadoc工具生成的API文檔將提取@Documented的使用信息
以上就是所有的元注解以及他們的作用分析,
有了這些元注解有什么用呢?
。。。。
當(dāng)然是為了方便我們在項(xiàng)目中自定義注解咯
那自定義注解怎么自定義呢?
下面我們來看看介紹如何自定義注解并利用注解完成一些實(shí)際的功能
語法:
1類修飾符 @interface 注解名稱{2 //成員變量,在注解中以無形參的形式存在3 //其方法名和返回值定義了該成員變的名字和類型4 String name();5 int age(); 6}
可以看到:注解的語法和接口的定義非常類似,他也一樣具備有作用域,但是它的成員變量的定義是以無形參的方法形式存在,名字定義了成員變量的名字,返回值定義了變量類型。
下面我們來自定義一個(gè)注解:
1@Target(ElementType.FIELD)2@Retention(RetentionPolicy.RUNTIME)3public @interface AnonTest {4 int age();5 String name();6}
注解@AnonTest在運(yùn)行時(shí)有效,作用域在成員變量上,它有兩個(gè)成員變量分別是int型的age和String型的name。
接下來我們就可以使用這個(gè)注解了
1public class test { 2 @AnonTest(age = 0, name = "1")3 public Integer tes;4}
這樣就是一個(gè)注解的定義和使用了,有人會疑惑說,spring中很多注解都是@xxx,為什么這個(gè)@AnonTest一定要要帶上兩個(gè)成員變量呢?
原因很簡單:
注解中的成員變量如果沒有默認(rèn)值,則在使用注解時(shí)必須要給成員變量賦值
但如果成員變量有默認(rèn)值,那可以直接在定義注解時(shí),賦值上去,這樣在使用時(shí)就可以省略不寫
1@Target(ElementType.FIELD)2@Inherited3@Retention(RetentionPolicy.RUNTIME)4public @interface AnonTest {5 int age() default 1;6 String name() default "注解辣雞";7}
這樣在調(diào)用注解時(shí)就可以不賦值
1public class test {23 @AnonTest4 public Integer tes;
上面看到,我們已經(jīng)使用了注解,但是我們并沒發(fā)現(xiàn)@AnonTest對我們的tes成員變量有任何作用,這是因?yàn)樽⒔獗旧碓诔绦蛑惺遣粫У?,而是需要程序來提取?shù)據(jù)并且處理注解本應(yīng)該做的工作。
因此
我們需要知道如何從注解中提取信息并且做相應(yīng)的處理。
Java使用Annotation接口來代表程序元素前面的注解,該接口是所有注解的父接口,該接口主要有以下幾個(gè)實(shí)現(xiàn)類:
- Class:類定義
- Constructor:構(gòu)造器定義
- Field:類成員變量定義
- Method:類的方法定義
- Package:類的包定義
AnnotatedElement接口是所有程序元素所實(shí)現(xiàn)的接口的父接口,所以程序通過反射獲取了某個(gè)類的AnnotatedElement對象之后,程序就可以調(diào)用該對象的如下幾個(gè)方法來訪問注解信息:
getAnnotation(Class
<t style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; font-size: inherit; color: inherit; line-height: inherit;">annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。</t>
getAnnotations():返回該程序元素上存在的所有注解。
- isAnnotationPresent(Class annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
- getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。
有了這些方法,我們就可以在類、方法、成員變量等程序元素中獲取到注解信息
比如:
1@Component 2@Service 3public class test { 4 5 @AnonTest 6 public Integer tes; 7 8 @Deprecated 9 public void m1(){1011 }1213 public static void main(String[] args) {14 try {15 //獲取所有test類上的注解16 Annotation[] ar=Class.forName("com.anon.test").getAnnotations();17 //獲取所有test類上m1方法的注解18 Annotation[] ar1=Class.forName("com.anon.test").getMethod("m1").getAnnotations();19 //遍歷所有的注解,檢測是否有我們想要的某個(gè)注解 如@Component注解20 for(Annotation a:ar){21 System.out.println(a.getClass());22 if(a instanceof Component)23 System.out.println("用了注解:"+a);24 }25 } catch (Exception e) {26 // TODO Auto-generated catch block27 e.printStackTrace();28 }29 }30}3132//輸出:33//用了注解:@org.springframework.stereotype.Component(value=)34//class com.sun.proxy.$Proxy4
利用AnnotatedElement提供的方法能獲取到我們想要的注解信息之后,就可以針對注解做一些特定的行為。
例如,對于所有的注冊用戶信息,系統(tǒng)需要把名稱和年齡上報(bào)到另一個(gè)年齡分布表中,就可以利用注解就可以完成這樣的動作
1public class test { 2 @AnonTest(name="張小龍",age=25) 3 public Integer tes; 4 public static void main(String[] args) { 5 test.getInfo(test.class); 6 } 7 public static void getInfo(Class<?> clazz) { 8 //獲取目標(biāo)類的所有成員變量 9 Field[] fields = clazz.getDeclaredFields();10 for (Field field : fields) {11 //查找變量中是否有存在@AnonTest注解12 if (field.isAnnotationPresent(AnonTest.class)) {13 //如果存在,則做相應(yīng)處理 14 AnonTest anon= field.getAnnotation(AnonTest.class);15 System.out.println("名稱是:"+anon.name());16 System.out.println("年齡是:"+anon.age());17 System.out.println("上報(bào)信息到用戶年齡分布表中");18 //上報(bào)信息到用戶年齡分布表中19 //uploadInfoToLog(anon.name()),anon.age())20 }21 }22 }23}
輸出:
1名稱是:張小龍2年齡是:253上報(bào)信息到用戶年齡分布表中
最終程序按照我們的需求運(yùn)行并且輸出正確的信息,由此可見,注解對于Java來說是一種補(bǔ)充,他的存在與否對程序來說應(yīng)該是無影響的,靈活使用注解能使開發(fā)的效率更高,而且程序規(guī)范性也得到增強(qiáng)。
強(qiáng)烈推薦:
1、Redis模糊查詢在生產(chǎn)環(huán)境出現(xiàn)嚴(yán)重性能問題
2、Linux性能的重要指標(biāo):打開文件數(shù)的限制
3、以Vue為例,解釋JavaScript的反應(yīng)性
5、18位身份證號藏什么玄機(jī)?用js教你校驗(yàn)
覺得本文對你有幫助?請分享給更多人
關(guān)注「編程無界」,提升裝逼技能