啊啊,终于搞明白了,原来注解是这么一回事。6000+字理解注解【一】
文章目錄
- 前言
- 復(fù)習(xí)注解
- 元注解
- @Retention 存活時(shí)間
- @Documented 文檔
- @Target 目標(biāo)
- @Inherited 繼承
- @Repeatable 可重復(fù)
- 注解屬性
- 注解實(shí)現(xiàn)-反射
- 反射獲取注解方法
- 注解的使用場(chǎng)景
- 后續(xù)
- 導(dǎo)航
前言
這兩天在gitHub上看一個(gè)開源項(xiàng)目,發(fā)現(xiàn)項(xiàng)目中有許多自定義注解。雖然在學(xué)JavaSE的時(shí)候?qū)W個(gè)注解,但是主要講的是如何自定義注解,和一些元注解的知識(shí)點(diǎn)。并沒有涉及到注解如何實(shí)現(xiàn)具體的功能。直到看到這個(gè)項(xiàng)目,突然醍醐灌頂。
由于篇幅過長(zhǎng),分成兩篇來(lái)寫。
復(fù)習(xí)注解
注解是在Java 1.5 的時(shí)候被引入的,注解的創(chuàng)建與接口十分相似。就是在interface關(guān)鍵字前面加一個(gè)@符號(hào)。
public @interface MyAnnotation{ }創(chuàng)建玩注解之后就可以在想要添加主機(jī)的地方使用了,但是想要讓注解能夠正常工作,還需要給它化化妝。什么是化妝那,這里就要引入元注解的一個(gè)概念了,元注解他也是一個(gè)注解,只不過它是用來(lái)修飾注解的注解。有點(diǎn)迷?不慌咱慢慢看。
元注解
元注解一共有五種分別為:
- @Retention
- @Documented
- @Target
- @Inherited
- @Repeatable
他們的使用方法就是在創(chuàng)建注解的時(shí)候在,所要?jiǎng)?chuàng)建的注解上使用他們:
@Retention 存活時(shí)間
Retention中文意思是保留的意思。當(dāng)@Retention應(yīng)用到一個(gè)注解上的時(shí)候 ,通過參數(shù)可以設(shè)置這個(gè)注解的存活時(shí)間。什么是存活時(shí)間,就是說通過不同參數(shù)可以確定@Retention修飾的注解在那種情況下會(huì)消失,是保留到源碼階段、還是編譯階段、還是加載到JVM那。
下面看一下它的參數(shù):
- RetentionPolicy.SOURCE
注解只被保留到編譯階段,當(dāng)編譯器編譯到它的時(shí)候看到參數(shù)為 RetentionPolicy.SOURCE會(huì)直接將其從源碼中剔除。
- RetentionPolicy.SOURCE
注解制備保留到編譯進(jìn)行的時(shí)候,也就是能保證在編譯期間注解也存在,但是不能進(jìn)入JVM中。
- RetentionPolicy.SOURCE
注解可以保留到程序中,它會(huì)被加載到JVM中從而在服務(wù)中起作用。
@Documented 文檔
這個(gè)注解,翻譯過來(lái)就是文檔。那他并沒有其他功能上的作用,主要作用就是能講注解中的元素包含到Javadoc中去。
@Target 目標(biāo)
Target 有目標(biāo)的意思,他的意思就是說,這個(gè)注解能夠使用的地方是哪里,是方法上、類上還是參數(shù)上。我們通過它的參數(shù)就可以進(jìn)行設(shè)置。
| ElementType.ANNOTATION_TYPE | 可以使用在注解上 |
| ElementType.CONSTRUCTOR | 可以在構(gòu)造方法上使用 |
| ElementType.FIELD | 可以在屬性上使用注解 |
| ElementType.LOCAL_VARIABLE | 作用在局部變量上 |
| ElementType.METHOD | 作用在方法上 |
| ElementType.PACKAGE | 作用在包上 |
| ElementType.PARAMETER | 作用在方法內(nèi)的參數(shù)上 |
| ElementType.TYPE | 可以給類型進(jìn)行注解,比如類、接口、枚舉 |
@Inherited 繼承
Inherited 意思為繼承,這個(gè)元注解的作用有點(diǎn)繞。就是說如果@Inherited注解作用與自定義注解@MyAnnotation上,然后在A類上使用了自定義注解,然后A類的子類,就相當(dāng)于也擁有@MyAnnotation注解。
/*自定義注解*/ @Inherited public @interface MyAnnotation{ } --------------------------- /*A類*/ @MyAnnotation public class A{ }/*B類繼承A類*/ public class B extent A{ }/* //此時(shí),B類也繼承了A類的注解。相當(dāng)于 @MyAnnotation public class B extent A{ } */@Repeatable 可重復(fù)
這個(gè)注解是在Java 1.8的時(shí)候加進(jìn)來(lái)的。那可重復(fù)是什么意思那,就是說使用注解的時(shí)候可以同時(shí)使用多次注解。
public class A{ @MyAnnotation("教師") @MyAnnotation("公務(wù)員")public void test(){}}如果想實(shí)現(xiàn)這樣的效果,那么就需要使用@Repeatable注解修飾@MyAnnotation注解。@MyAnnotation才可以在一個(gè)方法或其它地方使用多次。
注解屬性
注解的屬性其實(shí)就好像實(shí)體類里面的屬性,只是寫法上有稍微的不同。但是注解是沒有方法的,
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) ... ... public @interface MyAnnotation{ int id(); String value(); }注意這里id()和value()表示的不是方法而是屬性也可叫做注解的變量。
注解的賦值方法就是在使用注解的時(shí)候,通過注解中的對(duì)應(yīng)屬性="xxx"的形式
注解屬性可以設(shè)置默認(rèn)值,也就是如果使用注解時(shí)沒有賦予特定的值,就使用默認(rèn)值。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) ... ... public @interface MyAnnotation{ int id() default 101; String value(); }這里還有一個(gè)注意點(diǎn),如果注解中只有一個(gè)名字為value的屬性的時(shí)候,應(yīng)用這個(gè)注解的時(shí)候可以直接填寫屬性值。
pulic @interface MyAnnotation{String value; } @MyAnnotation("Hi") public void test(){ }如果定義的這個(gè)注解中沒有屬性值,我們?cè)谑褂米⒔獾臅r(shí)候就不用在寫括號(hào)了
@MyAnnotation public void test(){ }注解實(shí)現(xiàn)-反射
注解的知識(shí)經(jīng)過上面的內(nèi)容,應(yīng)該能夠有個(gè)了解。那么我們定義好后注解,也是用了,但是并沒有什么實(shí)質(zhì)的效果。哪有人該說了那定義有什么用?其實(shí)不然,如果真的沒有用那么Java官方就不會(huì)定義注解了。
好下面我們就給自定義的注解注入靈魂,實(shí)現(xiàn)當(dāng)使用注解后完成相應(yīng)的功能。那該如何實(shí)現(xiàn)那,注解中又不能寫方法,之有屬性值, 我們也不能通過new獲取注解。這里就要提到一個(gè)比較重要的知識(shí)點(diǎn)就是反射。
我們可以通過反射獲取作用在類或者方法上的注解讓,后進(jìn)行對(duì)應(yīng)的處理。
反射獲取注解方法
注解通過反射獲取。首先通過class對(duì)象的isAnnotationPresent()方法判斷他是否應(yīng)用了某注解。
public Boolean isAnnotationPresent(Class<? extent Annotation> annotationClass) {}然后通過getAnnotation()方法來(lái)獲取Annotation對(duì)象。
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {}或者是getAnnotations()方法
public Annotation[] getAnnotations(){}前一種方法返回指定類型的注解,后一種方法返回注解到這個(gè)元素上的所有注解。
如果獲取到的Annotation如果不為null,則就可以調(diào)用他們的屬性方法了。比如
運(yùn)行結(jié)果:
id:101上面是獲取類上的注解,其實(shí)方法、屬性的注解都是可以通過反射進(jìn)行獲取的。
自定注解:
編寫測(cè)試類
public class TestAnnotation {@MyAnnotation("Hi")public void testMethod(){System.out.println("fun");}public static void main(String[] args) throws NoSuchMethodException {Class<TestAnnotation> aClass = TestAnnotation.class;Method msg = aClass.getDeclaredMethod("testMethod");msg.setAccessible(true);MyAnnotation annotation = msg.getAnnotation(MyAnnotation.class);System.out.println(annotation.value());} }運(yùn)行結(jié)果
Hi這里需要注意一下,如果想要注解在運(yùn)行時(shí)能夠獲取到,那么必須加上@Retention(RetentionPolicy.RUNTIME)這個(gè)注解參數(shù),因?yàn)檫@個(gè)參數(shù)是指,注解能在jvm中被執(zhí)行。
注解的使用場(chǎng)景
到這里應(yīng)該大家對(duì)注解都有一定的了解了,但是還是可能會(huì)有疑惑注解到底有什么用吶。
我們不妨先看看官方的回答:
注解是一系列元數(shù)據(jù),它提供數(shù)據(jù)用來(lái)解釋程序代碼,但是注解并非是所解釋的代碼本身的一部分。注解對(duì)于代碼的運(yùn)行效果沒有直接影響。
注解有許多用處,主要如下:
- 提供信息給編譯器: 編譯器可以利用注解來(lái)探測(cè)錯(cuò)誤和警告信息
- 編譯階段時(shí)的處理: 軟件工具可以用來(lái)利用注解信息來(lái)生成代碼、Html文檔或者做其它相應(yīng)處理。
- 運(yùn)行時(shí)的處理: 某些注解可以在程序運(yùn)行的時(shí)候接受代碼的提取
值得注意的是,注解不是代碼本身的一部分。
從官方的話我們可以看出注解的作用并不是來(lái)寫主要業(yè)務(wù)的,而是通過注解實(shí)現(xiàn)一些對(duì)代碼的處理。
如果大家用過LomBok在實(shí)體類上面加一個(gè)@Data就可以幫助我們生成實(shí)體類的get和set的方法,在或者說Swagger生成接口文檔,在對(duì)應(yīng)的接口上加上對(duì)應(yīng)的注解就可以實(shí)現(xiàn)生成對(duì)應(yīng)的接口文檔。注解只是起到到了標(biāo)簽的作用,具體的實(shí)現(xiàn)方式是有對(duì)應(yīng)的程序獲取到注解這個(gè)標(biāo)識(shí),然后去處理產(chǎn)生的。
那么我們可以用注解做什么呢?
現(xiàn)在我們就動(dòng)手寫一個(gè)自己的注解,通過這個(gè)注解來(lái)檢測(cè)程序是否報(bào)錯(cuò)。這是一個(gè)簡(jiǎn)單的小案例。
首先寫一個(gè)注解@Jiance
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Jiance {}要測(cè)試的程序Cheshi并在方法上加上注解
public class Cheshi {@Jiancepublic void suanShu(){System.out.println("1234567890");}@Jiancepublic void jiafa(){System.out.println("1+1="+1+1);}@Jiancepublic void jiefa(){System.out.println("1-1="+(1-1));}@Jiancepublic void chengfa(){System.out.println("3 x 5="+ 3*5);}@Jiancepublic void chufa(){System.out.println("6 / 0="+ 6 / 0);}public void ziwojieshao(){System.out.println("我寫的程序沒有 bug!");} }運(yùn)行檢測(cè)程序TestJiance
public class TestJiance {public static void main(String[] args) {Cheshi cheshi = new Cheshi();Class<Cheshi> cheshiClass = Cheshi.class;Method[] declaredMethods = cheshiClass.getDeclaredMethods();StringBuilder log = new StringBuilder();log.append("**************日志****************\n");int num = 0;for (Method declaredMethod : declaredMethods) {Jiance annotation = declaredMethod.getAnnotation(Jiance.class);if (annotation!=null){try {declaredMethod.setAccessible(true);declaredMethod.invoke(cheshi,null);} catch (Exception e) {num++;log.append(declaredMethod.getName()+":error:"+e.getCause().getMessage()+"\n");}}}log.append("cheshi has "+num+ " error");System.out.println(log);} }執(zhí)行結(jié)果:
1+1=11 1234567890 1-1=0 3 x 5=15 **************日志**************** chufa:error:/ by zero cheshi has 1 error這樣我們就成了這小的案例。我們通過我們自定義的注解,來(lái)檢測(cè)所有cheshi類中有錯(cuò)的方法。
注解的作用主要取決于你想用它做什么。
后續(xù)
利用反射獲取注解并實(shí)現(xiàn)功能通過上面已經(jīng)實(shí)現(xiàn)了,但是還有一種方式也可以獲取注解,并實(shí)現(xiàn)對(duì)應(yīng)的功能邏輯。
那就是利用Spring框架的Aop來(lái)實(shí)現(xiàn),AOP面向切面編程,我們可以利用AOP做很多事情。那如果AOP遇到注解會(huì)發(fā)生什么那。
下一篇我們利用自定義注解和Aop實(shí)現(xiàn)接口防刷的功能。也就是在一段時(shí)間內(nèi)如果大量訪問接口,就會(huì)觸發(fā)保護(hù)的一個(gè)案例。
End!!! 如有疑問請(qǐng)留言評(píng)論!
導(dǎo)航
接口防刷案例【傳送門】
總結(jié)
以上是生活随笔為你收集整理的啊啊,终于搞明白了,原来注解是这么一回事。6000+字理解注解【一】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电子科技大学计算机专业分班,关于电子科技
- 下一篇: 跳转指令: JMP、JECXZ、JA、J