java零基礎入門-高級特性篇(十六) 類載入與反射 3

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

面向切面編程AOP

AOP - Aspect Oriented Programming的縮寫,java不是面向對象編程么,怎麼又整了個面向切面編程出來了?其實面向切面這種思想是對面向對象思想(OOP-Object Oriented Programming)的一種補充和擴展,讓程序在設計上更加靈活,使代碼編寫的難度降低,功能之間的耦合度降低。普通的業務邏輯都是串列的,一個邏輯接著一個邏輯,從上往下順序執行,有時需要新增功能時,不得不對每一個功能都進行修改,而AOP提供了另一種解決方案,它可以通過預編譯方式和運行期動態代理的方式, 實現在不修改源代碼的情況下給程序統一加上新功能。

功能改造

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

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

切面

無需改動原有代碼,新增切面即可,將記錄日誌的功能切入到所有需要記錄日誌的功能模塊中,這樣只需要在一個地方維護日誌模塊功能即可,不需要在每一個功能中都加上記錄日誌的功能。有了切面,如果後面還有這種需要在部分功能上都加上同樣功能的需求的時候,只需要增加切面即可。比如在購物的時候不需要驗證登錄,付款的時候驗證登錄就可以了,這時候就只有部分功能加入許可權驗證切面就可以了。

切面

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

代理

原告和被告都不懂法,官司沒法打,所以都請個代理律師,讓律師來完成申訴和辯護的任務。原告就相當於系統原有代碼,當系統需要統一增加功能的時候,請個代理律師來完成新的功能就行了,原告和被告是不需要做任何改變的,畢竟為了打個官司,總不能讓原告和被告先去學一遍法律知識吧。

原功能

1.首先是登錄功能的介面,用來為登錄模塊創建模板。2.User實體類,用來傳遞用戶信息。3.具體的登錄功能實現,如果需要新增日誌功能的話,需要在每個方法上面都加上日誌記錄的代碼。4.沒有日誌記錄功能的代碼,所以列印出來的只有用戶具體的狀態。具體的功能都在實現類裡面,要增加功能也是在實現類中添加,所以這個實現類就是需要被代理的類。下面就來看看如何設計一個代理類,來為這個實現類服務。

切面

這個類LoginInvocationHandler需要實現InvocationHandler類,然後重寫invoke方法。在構造器中需要傳入被代理的對象,可以通過反射獲取被代理對象的信息,invoke方法替代了原來需要被調用的被代理對象的方法,這樣就可以在被代理對象的方法中添加新的功能而無需改動原有的代碼。

使用代理調用方法

在調用方法的時候,不要再直接調用原對象的方法,而需要根據切面設置代理,通過反射根據介面創建新的代理對象,此時生成的對象在運行時具備了代理對象新增的功能,最後使用代理對象調用方法即可。在結果中也看到了,新的功能已經生效,在用戶的登錄和退出之前和之後,都已經加上了日誌記錄。這個例子很好的體現了反射功能的強大,用反射實現了代理最終實現了面向切面的功能添加。

註解

在上面切面製作的類中,實現InvocationHandler介面的時候,有一個奇怪的東西。在實現介面的方法的時候,會有一個@Override在方法上面。這是個什麼玩意?這種代碼叫做註解Annotation,它可以在不改變原有代碼邏輯的情況下,對代碼進行一些補充或功能的添加。註解的功能十分強大,java的jdk有部分註解用於對代碼進行修飾,而他的功能強大之處主要體現在它的自定義功能。由於可以自定註解,在各大框架中有大量的封裝好的註解供我們使用,極大的方便了我們的開發和提高開發效率。

常用java自帶註解

@Override:限定重寫父類方法。抽象類中有抽象方法,也可以有實現好的方法,如果繼承了抽象類,那麼已實現的方法可以重寫也可以不重寫,而抽象方法必須被重寫,介面也是一樣,介面中都是抽象方法,在實現介面的時候必須重寫介面的抽象方法。而這個註解就是標識出必須被子類重寫的父類方法或實現介面的方法,它可以幫助我們避免忘記,或寫錯需要重寫方法帶來的問題。其實IDE工具一般都有自動完成重寫方法的功能,在自動完成的過程中也會自動加上這個註解,表示已重寫該方法。

@Override註解

@Deprecated:標識已過時的類或方法。在使用java的早期版本類或方法的時候會發現有一些類或方法是帶有一個刪除線的,因為他們已經被打上了@Deprecated註解。這些類或方法表示java已經不再推薦使用,在以後的版本有可能會將這種過時的類刪除,所以在寫代碼的過程中要盡量避免使用這種類或方法。

@Deprecated註解

@SuppressWarnings:取消編譯警告。這個註解可以在類上,方法上也可以在變數上,當出現編譯警告的時候,通過這個註解就可以告訴編譯器別給我警告了,我知道了。此註解會作用到標記位置的所有作用範圍,如果在類上,整個類都被取消警告,在方法上,整個方法取消警告。這個註解最常見的時候就是當我們聲明一個集合併且不指定泛型,這個時候就會出現警告,如果確實不想指定泛型最好加上此註解。在程序出現多條警告的時候,還可以同時取消多個編譯警告,在註解上傳入一個字元串數組即可。

@SuppressWarnings註解

自定義註解

java自身的自定義註解功能有限,結合一些框架以後會有更加強大的功能。但是有些簡單的功能,依靠java自身也是可以完成的,下面來看看如何一個自定義的註解。

自定義註解肯定會用到元註解,元註解就是用來修飾註解的註解。也就是說定義一個註解的時候需要依賴別的註解,這些被依賴的註解就是元註解。常用的元註解有下面四種。

@Documented – 如果一個類型添加了Documented註解,那麼它的註解會成為元素API的一部分。可以被工具文檔化,在生成文檔的時候會將信息自動生成到API中。

@Target – 指定該註解可以使用在什麼地方,比如是在方法上的註解還是類上的註解。如果不指定此元註解,標識該註解可以用在任何元素上。下面是可以指定的位置。

TYPE:類,介面或者枚舉 FIELD:域,包含枚舉常量 METHOD:方法

PARAMETER:參數 CONSTRUCTOR:構造方法 PACKAGE:包

LOCAL_VARIABLE:局部變數 ANNOTATION_TYPE:註解類型

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

@Retention – 表示註解的聲明周期。有三種方式,SOURCE:編譯完就失效。CLASS:編譯級別保留,在jvm運行時失效,默認值。RUNTIME: 運行級別保留,編譯後的class文件中存在,在jvm運行時保留,可以被反射調用。

下面來定義一個註解,來幫助我們檢查欄位的賦值是否滿足要求。通常在web項目中,會有大量的數據從前端傳遞給後端,數據校驗就是一個十分重要的環節。比如在用戶進行註冊的時候,就必須進行數據校驗,比如用戶名的長度,密碼複雜度等等,都需要滿足要求才能夠發送到後端,而後端也必須再一次對這些數據進行校驗。

但是,通常對這些數據進行校驗會寫在業務中,造成業務代碼中充斥了大量的if,else的判斷,這樣不僅在業務中寫了大量與業務無關的代碼,還會有大量重複的代碼。比如註冊的時候校驗密碼,需要在註冊功能中校驗一遍,再修改密碼的時候,又要在修改密碼的功能中再次校驗,如果能夠將校驗規則提取出來,不僅使得代碼更加專註於業務,還能大幅提高編碼效率。如果將校驗規則封裝進方法,也會有參數傳遞,方法調用等邏輯,如果在對對象進行賦值的時候,就進行校驗,這樣會更加的優雅,沒有任何的校驗邏輯在代碼中。如何做到?使用註解。

數據校驗

在對象賦值的時候,將校驗邏輯封裝進註解,在屬性上註解後,在對屬性進行賦值的時候就會進行數據校驗,對實體類的封裝性沒有任何的破壞,也不會破壞單一職責原則,因為實體類本身裡面是沒有任何校驗邏輯的。

1.首先定義自定義註解。@Target指定此註解用在欄位上,@Retention指定為RUNTIME,運行時可以使用反射來調用。註解中指定可以在註解上使用的參數,參數類型只能是基本類型,boolean是基本類型可以使用。isNotNull()是屬性名,default false表示如果註解上不使用此屬性,則默認為false。這樣一個自定義註解就定義好了。

自定義註解

2.校驗器,封裝註解中具體的校驗邏輯。

註解邏輯

3.使用自定義註解。在需要驗證的欄位上加上註解,並且對註解屬性進行賦值。如果沒有聲明註解屬性,則使用定義註解時的屬性默認值。

使用自定義註解

4.校驗賦值。在對象賦值的後,使用校驗器對對象進行校驗。

校驗

其實在參數校驗方面,已經有很成熟的標準框架可以直接使用,比如JSR303 ,就是一套JavaBean參數校驗的標準。除了JSR303內置了大量現成的註解之外,還可以非常靈活的對校驗註解進行擴展,定義自己需要的驗證註解。這一套註解的使用方法跟上例中我們自定義的註解使用方法類型,但是用法更方便,無需使用驗證器傳入對象進行校驗,在註解中就可以定義需要拋出的異常信息等等。JSR303已有註解如下

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

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

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

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

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

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

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

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

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

@Digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的範圍內

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

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

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

各位只需要了解即可,在項目中需要進行參數校驗的時候再來查閱即可。


推薦閱讀:
相关文章