類加載與反射 3

java零基礎(chǔ)入門-高級(jí)特性篇(十六)? 類加載與反射 3

如果你認(rèn)為反射只有前面介紹的那些作用,那么就太小看這個(gè)功能了。本章再來介紹反射中更加強(qiáng)大的用法,反射功能可以在設(shè)計(jì)層面更好的處理一些難題,甚至改變編程的方式。

面向切面編程AOP

AOP - Aspect Oriented Programming的縮寫,java不是面向?qū)ο缶幊堂?,怎么又整了個(gè)面向切面編程出來了?其實(shí)面向切面這種思想是對(duì)面向?qū)ο笏枷耄∣OP-Object Oriented Programming)的一種補(bǔ)充和擴(kuò)展,讓程序在設(shè)計(jì)上更加靈活,使代碼編寫的難度降低,功能之間的耦合度降低。普通的業(yè)務(wù)邏輯都是串行的,一個(gè)邏輯接著一個(gè)邏輯,從上往下順序執(zhí)行,有時(shí)需要新增功能時(shí),不得不對(duì)每一個(gè)功能都進(jìn)行修改,而AOP提供了另一種解決方案,它可以通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理的方式, 實(shí)現(xiàn)在不修改源代碼的情況下給程序統(tǒng)一加上新功能。?

功能改造

假設(shè)現(xiàn)在有一個(gè)系統(tǒng)有登錄,購物,付款,退出這幾個(gè)功能。然后有一天,突然有用戶說我為什么沒登錄就可以付錢?我東西寄哪去了?我明明付了錢為啥說我沒付錢?這時(shí)候怎么辦?如果沒有一套完善的日志系統(tǒng),我們無從查起,不是知道具體哪個(gè)環(huán)節(jié)出了問題,所以需要對(duì)用戶的每一步的操作都記錄日志,這樣出了問題就可以精確的追蹤到出問題的地方。這要怎么改造系統(tǒng)?那還不簡(jiǎn)單~加班唄,每個(gè)具體功能前后全加上日志,一旦用戶有操作就給記錄日志。說的很輕松,但是要知道上圖只是一個(gè)簡(jiǎn)單的例子,真正的系統(tǒng)可是十分龐大的,幾十個(gè)模塊都算少的,比如物流,倉庫,財(cái)務(wù),會(huì)員等等,一旦用戶付了錢買了東西,一大片業(yè)務(wù)都需要同時(shí)改變,更新物流信息,庫存減少,財(cái)務(wù)入賬,銀行扣款,會(huì)員積分增加,等等等,你確定你要全公司一起日以繼夜的加班新增功能么?

面向切面AOP告訴你,不需要!只需要一個(gè)程序員敲幾行代碼即可。是不是覺得有點(diǎn)不可思議?這么多模塊都需要加日志,幾行代碼怎么可能做得完?其實(shí)如果使用AOP的思想來考慮這個(gè)問題,這個(gè)問題會(huì)簡(jiǎn)單很多,下面看看AOP如何解決。

切面

無需改動(dòng)原有代碼,新增切面即可,將記錄日志的功能切入到所有需要記錄日志的功能模塊中,這樣只需要在一個(gè)地方維護(hù)日志模塊功能即可,不需要在每一個(gè)功能中都加上記錄日志的功能。有了切面,如果后面還有這種需要在部分功能上都加上同樣功能的需求的時(shí)候,只需要增加切面即可。比如在購物的時(shí)候不需要驗(yàn)證登錄,付款的時(shí)候驗(yàn)證登錄就可以了,這時(shí)候就只有部分功能加入權(quán)限驗(yàn)證切面就可以了。

切面

在講具體代碼之前,除了AOP,還有一個(gè)知識(shí)需要了解一下,那就是代理,AOP就是代理模式的一種具體實(shí)現(xiàn)。那么代理模式又是個(gè)什么?代理不懂,代理律師總聽過吧?打官司,自己不懂法,需要找一個(gè)專業(yè)的律師幫忙打官司,自己付錢等結(jié)果就行了。這種不直接由自己出面解決問題,而使用中介的方式讓中介來完成本該由自己完成工作的模式,就是代理模式。代理模式提供了一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用,最后由代理對(duì)象完成任務(wù)。

代理

原告和被告都不懂法,官司沒法打,所以都請(qǐng)個(gè)代理律師,讓律師來完成申訴和辯護(hù)的任務(wù)。原告就相當(dāng)于系統(tǒng)原有代碼,當(dāng)系統(tǒng)需要統(tǒng)一增加功能的時(shí)候,請(qǐng)個(gè)代理律師來完成新的功能就行了,原告和被告是不需要做任何改變的,畢竟為了打個(gè)官司,總不能讓原告和被告先去學(xué)一遍法律知識(shí)吧。

原功能

1.首先是登錄功能的接口,用來為登錄模塊創(chuàng)建模板。2.User實(shí)體類,用來傳遞用戶信息。3.具體的登錄功能實(shí)現(xiàn),如果需要新增日志功能的話,需要在每個(gè)方法上面都加上日志記錄的代碼。4.沒有日志記錄功能的代碼,所以打印出來的只有用戶具體的狀態(tài)。具體的功能都在實(shí)現(xiàn)類里面,要增加功能也是在實(shí)現(xiàn)類中添加,所以這個(gè)實(shí)現(xiàn)類就是需要被代理的類。下面就來看看如何設(shè)計(jì)一個(gè)代理類,來為這個(gè)實(shí)現(xiàn)類服務(wù)。


切面

這個(gè)類LoginInvocationHandler需要實(shí)現(xiàn)InvocationHandler類,然后重寫invoke方法。在構(gòu)造器中需要傳入被代理的對(duì)象,可以通過反射獲取被代理對(duì)象的信息,invoke方法替代了原來需要被調(diào)用的被代理對(duì)象的方法,這樣就可以在被代理對(duì)象的方法中添加新的功能而無需改動(dòng)原有的代碼。

使用代理調(diào)用方法

在調(diào)用方法的時(shí)候,不要再直接調(diào)用原對(duì)象的方法,而需要根據(jù)切面設(shè)置代理,通過反射根據(jù)接口創(chuàng)建新的代理對(duì)象,此時(shí)生成的對(duì)象在運(yùn)行時(shí)具備了代理對(duì)象新增的功能,最后使用代理對(duì)象調(diào)用方法即可。在結(jié)果中也看到了,新的功能已經(jīng)生效,在用戶的登錄和退出之前和之后,都已經(jīng)加上了日志記錄。這個(gè)例子很好的體現(xiàn)了反射功能的強(qiáng)大,用反射實(shí)現(xiàn)了代理最終實(shí)現(xiàn)了面向切面的功能添加。

注解

在上面切面制作的類中,實(shí)現(xiàn)InvocationHandler接口的時(shí)候,有一個(gè)奇怪的東西。在實(shí)現(xiàn)接口的方法的時(shí)候,會(huì)有一個(gè)@Override在方法上面。這是個(gè)什么玩意?這種代碼叫做注解Annotation,它可以在不改變?cè)写a邏輯的情況下,對(duì)代碼進(jìn)行一些補(bǔ)充或功能的添加。注解的功能十分強(qiáng)大,java的jdk有部分注解用于對(duì)代碼進(jìn)行修飾,而他的功能強(qiáng)大之處主要體現(xiàn)在它的自定義功能。由于可以自定注解,在各大框架中有大量的封裝好的注解供我們使用,極大的方便了我們的開發(fā)和提高開發(fā)效率。

常用java自帶注解

@Override:限定重寫父類方法。抽象類中有抽象方法,也可以有實(shí)現(xiàn)好的方法,如果繼承了抽象類,那么已實(shí)現(xiàn)的方法可以重寫也可以不重寫,而抽象方法必須被重寫,接口也是一樣,接口中都是抽象方法,在實(shí)現(xiàn)接口的時(shí)候必須重寫接口的抽象方法。而這個(gè)注解就是標(biāo)識(shí)出必須被子類重寫的父類方法或?qū)崿F(xiàn)接口的方法,它可以幫助我們避免忘記,或?qū)戝e(cuò)需要重寫方法帶來的問題。其實(shí)IDE工具一般都有自動(dòng)完成重寫方法的功能,在自動(dòng)完成的過程中也會(huì)自動(dòng)加上這個(gè)注解,表示已重寫該方法。

@Override注解

@Deprecated:標(biāo)識(shí)已過時(shí)的類或方法。在使用java的早期版本類或方法的時(shí)候會(huì)發(fā)現(xiàn)有一些類或方法是帶有一個(gè)刪除線的,因?yàn)樗麄円呀?jīng)被打上了@Deprecated注解。這些類或方法表示java已經(jīng)不再推薦使用,在以后的版本有可能會(huì)將這種過時(shí)的類刪除,所以在寫代碼的過程中要盡量避免使用這種類或方法。

@Deprecated注解


@SuppressWarnings:取消編譯警告。這個(gè)注解可以在類上,方法上也可以在變量上,當(dāng)出現(xiàn)編譯警告的時(shí)候,通過這個(gè)注解就可以告訴編譯器別給我警告了,我知道了。此注解會(huì)作用到標(biāo)記位置的所有作用范圍,如果在類上,整個(gè)類都被取消警告,在方法上,整個(gè)方法取消警告。這個(gè)注解最常見的時(shí)候就是當(dāng)我們聲明一個(gè)集合并且不指定泛型,這個(gè)時(shí)候就會(huì)出現(xiàn)警告,如果確實(shí)不想指定泛型最好加上此注解。在程序出現(xiàn)多條警告的時(shí)候,還可以同時(shí)取消多個(gè)編譯警告,在注解上傳入一個(gè)字符串?dāng)?shù)組即可。

@SuppressWarnings注解

自定義注解

java自身的自定義注解功能有限,結(jié)合一些框架以后會(huì)有更加強(qiáng)大的功能。但是有些簡(jiǎn)單的功能,依靠java自身也是可以完成的,下面來看看如何一個(gè)自定義的注解。

自定義注解肯定會(huì)用到元注解,元注解就是用來修飾注解的注解。也就是說定義一個(gè)注解的時(shí)候需要依賴別的注解,這些被依賴的注解就是元注解。常用的元注解有下面四種。

@Documented – 如果一個(gè)類型添加了Documented注解,那么它的注解會(huì)成為元素API的一部分??梢员还ぞ呶臋n化,在生成文檔的時(shí)候會(huì)將信息自動(dòng)生成到API中。

@Target – 指定該注解可以使用在什么地方,比如是在方法上的注解還是類上的注解。如果不指定此元注解,標(biāo)識(shí)該注解可以用在任何元素上。下面是可以指定的位置。

TYPE:類,接口或者枚舉????FIELD:域,包含枚舉常量????METHOD:方法????

PARAMETER:參數(shù)????CONSTRUCTOR:構(gòu)造方法????PACKAGE:包

LOCAL_VARIABLE:局部變量????ANNOTATION_TYPE:注解類型

@Inherited – 子類繼承父類中的注解。

@Retention – 表示注解的聲明周期。有三種方式,SOURCE:編譯完就失效。CLASS:編譯級(jí)別保留,在jvm運(yùn)行時(shí)失效,默認(rèn)值。RUNTIME:?運(yùn)行級(jí)別保留,編譯后的class文件中存在,在jvm運(yùn)行時(shí)保留,可以被反射調(diào)用。

下面來定義一個(gè)注解,來幫助我們檢查字段的賦值是否滿足要求。通常在web項(xiàng)目中,會(huì)有大量的數(shù)據(jù)從前端傳遞給后端,數(shù)據(jù)校驗(yàn)就是一個(gè)十分重要的環(huán)節(jié)。比如在用戶進(jìn)行注冊(cè)的時(shí)候,就必須進(jìn)行數(shù)據(jù)校驗(yàn),比如用戶名的長(zhǎng)度,密碼復(fù)雜度等等,都需要滿足要求才能夠發(fā)送到后端,而后端也必須再一次對(duì)這些數(shù)據(jù)進(jìn)行校驗(yàn)。

但是,通常對(duì)這些數(shù)據(jù)進(jìn)行校驗(yàn)會(huì)寫在業(yè)務(wù)中,造成業(yè)務(wù)代碼中充斥了大量的if,else的判斷,這樣不僅在業(yè)務(wù)中寫了大量與業(yè)務(wù)無關(guān)的代碼,還會(huì)有大量重復(fù)的代碼。比如注冊(cè)的時(shí)候校驗(yàn)密碼,需要在注冊(cè)功能中校驗(yàn)一遍,再修改密碼的時(shí)候,又要在修改密碼的功能中再次校驗(yàn),如果能夠?qū)⑿r?yàn)規(guī)則提取出來,不僅使得代碼更加專注于業(yè)務(wù),還能大幅提高編碼效率。如果將校驗(yàn)規(guī)則封裝進(jìn)方法,也會(huì)有參數(shù)傳遞,方法調(diào)用等邏輯,如果在對(duì)對(duì)象進(jìn)行賦值的時(shí)候,就進(jìn)行校驗(yàn),這樣會(huì)更加的優(yōu)雅,沒有任何的校驗(yàn)邏輯在代碼中。如何做到?使用注解。

數(shù)據(jù)校驗(yàn)

在對(duì)象賦值的時(shí)候,將校驗(yàn)邏輯封裝進(jìn)注解,在屬性上注解后,在對(duì)屬性進(jìn)行賦值的時(shí)候就會(huì)進(jìn)行數(shù)據(jù)校驗(yàn),對(duì)實(shí)體類的封裝性沒有任何的破壞,也不會(huì)破壞單一職責(zé)原則,因?yàn)閷?shí)體類本身里面是沒有任何校驗(yàn)邏輯的。

1.首先定義自定義注解。@Target指定此注解用在字段上,@Retention指定為RUNTIME,運(yùn)行時(shí)可以使用反射來調(diào)用。注解中指定可以在注解上使用的參數(shù),參數(shù)類型只能是基本類型,boolean是基本類型可以使用。isNotNull()是屬性名,default false表示如果注解上不使用此屬性,則默認(rèn)為false。這樣一個(gè)自定義注解就定義好了。

自定義注解

2.校驗(yàn)器,封裝注解中具體的校驗(yàn)邏輯。

注解邏輯

3.使用自定義注解。在需要驗(yàn)證的字段上加上注解,并且對(duì)注解屬性進(jìn)行賦值。如果沒有聲明注解屬性,則使用定義注解時(shí)的屬性默認(rèn)值。

使用自定義注解

4.校驗(yàn)賦值。在對(duì)象賦值的后,使用校驗(yàn)器對(duì)對(duì)象進(jìn)行校驗(yàn)。

校驗(yàn)

其實(shí)在參數(shù)校驗(yàn)方面,已經(jīng)有很成熟的標(biāo)準(zhǔn)框架可以直接使用,比如JSR303 ,就是一套JavaBean參數(shù)校驗(yàn)的標(biāo)準(zhǔn)。除了JSR303內(nèi)置了大量現(xiàn)成的注解之外,還可以非常靈活的對(duì)校驗(yàn)注解進(jìn)行擴(kuò)展,定義自己需要的驗(yàn)證注解。這一套注解的使用方法跟上例中我們自定義的注解使用方法類型,但是用法更方便,無需使用驗(yàn)證器傳入對(duì)象進(jìn)行校驗(yàn),在注解中就可以定義需要拋出的異常信息等等。JSR303已有注解如下

@Null 被注釋的元素必須為 null

@NotNull 被注釋的元素必須不為 null

@AssertTrue 被注釋的元素必須為 true

@AssertFalse 被注釋的元素必須為 false

@Min(value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值

@Max(value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值

@DecimalMin(value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值

@DecimalMax(value) 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值

@Size(max, min) 被注釋的元素的大小必須在指定的范圍內(nèi)

@Digits (integer, fraction) 被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi)

@Past 被注釋的元素必須是一個(gè)過去的日期

@Future 被注釋的元素必須是一個(gè)將來的日期

@Pattern(value) 被注釋的元素必須符合指定的正則表達(dá)式

各位只需要了解即可,在項(xiàng)目中需要進(jìn)行參數(shù)校驗(yàn)的時(shí)候再來查閱即可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容