1. 程式人生 > >spring精講課程(續)

spring精講課程(續)

第3章 AOP 面向切面程式設計
3.1 動態代理
動態代理作用:功能增強和控制訪問
3.1.1 JDK 動態代理(理解)
動態代理是指,程式在整個執行過程中根本就不存在目標類的代理類,目標物件的代理
物件只是由代理生成工具(不是真實定義的類)在程式執行時由 JVM 根據反射等機制動態
生成的。代理物件與目標物件的代理關係在程式執行時才確立。
對比靜態代理,靜態代理是指在程式執行前就已經定義好了目標類的代理類。代理類
與目標類的代理關係在程式執行之前就確立了。
動態代理的實現方式常用的有兩種:使用 JDK 的 Proxy,與通過 CGLIB 生成代理。
Jdk 的動態要求目標物件必須實現介面,這是 java 設計上的要求。
從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類支援代理模式 Proxy, Method
和 InovcationHandler。
A、通過 JDK 的 java.lang.reflect.Proxy 類實現動態代理,會使用其靜態方法
newProxyInstance(),依據目標物件、業務介面及呼叫處理器三者,自動生成一個動態代理物件。
public static newProxyInstance ( ClassLoaderloader, Class<?>[]interfaces,
InvocationHandlerhandler)
loader:目標類的類載入器,通過目標物件的反射可獲取
interfaces:目標類實現的介面陣列,通過目標物件的反射可獲取
handler:呼叫處理器。
B、 InvocationHandler 是個介面,其具體介紹如下:
實現了 InvocationHandler 介面的類用於加強目標類的主業務邏輯。這個介面中有一個
方法 invoke(),具體加強的程式碼邏輯就是定義在該方法中的。程式呼叫主業務邏輯時,會自
動呼叫 invoke()方法。
//Object proxy:代理物件
//Method m :呼叫的方法
//Object [] args: 呼叫方法的引數
public Object invoke(Object proxy, Method m, Object[] args)
C、 Method 類物件,該類有一個方法也叫 invoke(),可以呼叫目標類的目標方法。
這兩個 invoke()方法,雖然同名,但無關。
public Object invoke ( Object obj, Object… args)
obj:表示目標物件
args:表示目標方法引數,就是其上一層 invoke 方法的第三個引數
在這裡插入圖片描述

3.1.2 CGLIB 動態代理(理解)
CGLIB(Code Generation Library)是一個開源專案。是一個強大的,高效能,高質量的 Code
生成類庫,它可以在執行期擴充套件 Java 類與實現 Java 介面。它廣泛的被許多 AOP 的框架
使用,例如 Spring AOP。
使用 JDK 的 Proxy 實現代理,要求目標類與代理類實現相同的介面。若目標類不存在
介面,則無法使用該方式實現。但對於無介面的類,要為其建立動態代理,就要使用 CGLIB 來實現。
CGLIB 代理的生成原理是生成目標類的子類,而子類是增強過的,這個子類物件就是代理物件。所以,使用 CGLIB 生成動態代理,要求目標類必須能夠被繼承,即不能是 final 的類。
CGLIB 經常被應用在框架中,例如 Spring ,Hibernate 等。cglib 的代理效率高於 Jdk。
專案中直接使用動態代理的地方不多。一般都使用框架提供的功能。
(1) 匯入 jar
在這裡插入圖片描述
(2) 定義目標類
SomeSerivce.java ,有 String doSome()方法
(3) 定義呼叫處理器,實現 MethodInterceptor 介面
在這裡插入圖片描述(5) 定義客戶端類
在這裡插入圖片描述3.2 AOP 引入(理解)
Step1:專案 aop_leadin1
先定義好介面與一個實現類,該實現類中除了要實現介面中的方法外,還要再寫兩個非
業務方法。非業務方法也稱為交叉業務邏輯:
1> doTransaction():用於事務處理
2> doLog():用於日誌處理
然後,再使介面方法呼叫它們。介面方法也稱為主業務邏輯
在這裡插入圖片描述
Step2:專案 aop_leadin2
當然,也可以有另一種解決方案:將這些交叉業務邏輯程式碼放到專門的工具類或處理類中,由主業務邏輯呼叫。
在這裡插入圖片描述
Step3:專案 aop_leadin3
以上的解決方案,還是存在弊端:交叉業務與主業務深度耦合在一起。當交叉業務邏輯
較多時,在主業務程式碼中會出現大量的交叉業務邏輯程式碼呼叫語句,大大影響了主業務邏輯
的可讀性,降低了程式碼的可維護性,同時也增加了開發難度。
所以,可以採用動態代理方式。在不修改主業務邏輯的前提下,擴充套件和增強其功能。
在這裡插入圖片描述在這裡插入圖片描述AOP 為 Aspect Oriented Programming 的縮寫,意為:面向切面程式設計,通過預編譯方式和執行期動態代理實現程式功能的統一維護的一種技術。AOP 是 OOP 的延續,是軟體開發中的一個熱點,也是 Spring 框架中的一個重要內容。利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
面向切面程式設計,就是將交叉業務邏輯封裝成切面,利用 AOP 容器的功能將切面織入到主業務邏輯中。所謂交叉業務邏輯是指,通用的、與主業務邏輯無關的程式碼,如安全檢查、事務、日誌、快取等。
若不使用 AOP,則會出現程式碼糾纏,即交叉業務邏輯與主業務邏輯混合在一起。這樣,會使主業務邏輯變的混雜不清。
例如,轉賬,在真正轉賬業務邏輯前後,需要許可權控制、日誌記錄、載入事務、結束事務等交叉業務邏輯,而這些業務邏輯與主業務邏輯間並無直接關係。但,它們的程式碼量所佔比重能達到總程式碼量的一半甚至還多。它們的存在,不僅產生了大量的“冗餘”程式碼,還大大幹擾了主業務邏輯—轉賬。
AOP 程式設計術語(掌握)
(1) 切面(Aspect)
切面泛指交叉業務邏輯。上例中的事務處理、日誌處理就可以理解為切面。常用的切面
是通知(Advice)。實際就是對主業務邏輯的一種增強。
(2) 織入(Weaving)
織入是指將切面程式碼插入到目標物件的過程。上例中MyInvocationHandler類中的invoke()
方法完成的工作,就可以稱為織入。
(3) 連線點(JoinPoint)
連線點指可以被切面織入的具體方法。通常業務介面中的方法均為連線點
(4) 切入點(Pointcut)
切入點指宣告的一個或多個連線點的集合。通過切入點指定一組方法。
被標記為 final 的方法是不能作為連線點與切入點的。因為最終的是不能被修改的,不
能被增強的。
(5) 目標物件(Target)
目 標 對 象 指 將 要 被 增 強 的 對 象 。 即 包 含 主 業 務 邏 輯 的 類 的 對 象 。 上 例 中 的
StudentServiceImpl 的物件若被增強,則該類稱為目標類,該類物件稱為目標物件。當然,
不被增強,也就無所謂目標不目標了。
(6) 通知(Advice)
通知是切面的一種實現,可以完成簡單織入功能(織入功能就是在這裡完成的)。上例
中的 MyInvocationHandler 就可以理解為是一種通知。換個角度來說,通知定義了增強程式碼
切入到目的碼的時間點,是目標方法執行之前執行,還是之後執行等。通知型別不同,切
入時間不同。
切入點定義切入的位置,通知定義切入的時間。
3.5 AspectJ 對 AOP 的實現(掌握)
對於 AOP 這種程式設計思想,很多框架都進行了實現。Spring 就是其中之一,可以完成面向
切面程式設計。然而,AspectJ 也實現了 AOP 的功能,且其實現方式更為簡捷,使用更為方便,
而且還支援註解式開發。所以,Spring 又將 AspectJ 的對於 AOP 的實現也引入到了自己的框
架中。
在 Spring 中使用 AOP 開發時,一般使用 AspectJ 的實現方式。
3.5.1 AspectJ 簡介
AspectJ 是一個面向切面的框架,它擴充套件了 Java 語言。AspectJ 定義了 AOP 語法,它有一個專門的編譯器用來生成遵守 Java 位元組編碼規範的 Class 檔案
官網地址:http://www.eclipse.org/aspectj/
AspetJ 是 Eclipse 的開源專案,官網介紹如下:
在這裡插入圖片描述a seamless aspect-oriented extension to the Javatm programming language(一種基於 Java 平臺的面向切面程式設計的語言)
Java platform compatible(相容 Java 平臺,可以無縫擴充套件)
easy to learn and use(易學易用)
3.5.2 AspectJ 的通知型別(理解)
AspectJ 中常用的通知有五種型別:
(1)前置通知
(2)後置通知
(3)環繞通知
(4)異常通知
(5)最終通知
3.5.3 AspectJ 的切入點表示式(掌握)
AspectJ 定義了專門的表示式用於指定切入點。表示式的原型是:
execution ( [modifiers-pattern] 訪問許可權型別
ret-type-pattern 返回值型別
[declaring-type-pattern] 全限定性類名
name-pattern(param-pattern)方法名(引數名)
[throws-pattern] 丟擲異常型別
)
切入點表示式要匹配的物件就是目標方法的方法名。所以,execution 表示式中明顯就
是方法的簽名。注意,表示式中加[ ]的部分表示可省略部分,各部分間用空格分開。在其中
可以使用以下符號:
在這裡插入圖片描述execution(public * (…))
指定切入點為:任意公共方法。
execution(
set*(…))
指定切入點為:任何一個以“set”開始的方法。
execution(* com.xyz.service..(…))
指定切入點為:定義在 service 包裡的任意類的任意方法。
execution(* com.xyz.service….(…))
指定切入點為:定義在 service 包或者子包裡的任意類的任意方法。“…”出現在類名中時,後面必須跟“”,表示包、子包下的所有類。
execution(
.service..(…))
指定只有一級包下的 serivce 子包下所有類(介面)中所有方法為切入點
execution(
…service..(…))
指定所有包下的 serivce 子包下所有類(介面)中所有方法為切入點
execution(
.ISomeService.(…))
指定只有一級包下的 ISomeSerivce 介面中所有方法為切入點
execution(* …ISomeService.(…))
指定所有包下的 ISomeSerivce 介面中所有方法為切入點
execution(* com.xyz.service.IAccountService.(…))
指定切入點為:IAccountService 介面中的任意方法。
execution(
com.xyz.service.IAccountService+.(…))
指定切入點為:IAccountService 若為介面,則為介面中的任意方法及其所有實現類中的任意
方法;若為類,則為該類及其子類中的任意方法。
execution(
joke(String,int)))
指定切入點為:所有的 joke(String,int)方法,且 joke()方法的第一個引數是 String,第二個參
數是 int。如果方法中的引數型別是 java.lang 包下的類,可以直接使用類名,否則必須使用
全限定類名,如 joke( java.util.List, int)。
execution(* joke(String,)))
指定切入點為:所有的 joke()方法,該方法第一個引數為 String,第二個引數可以是任意類
型,如joke(String s1,String s2)和joke(String s1,double d2)都是,但joke(String s1,double d2,String
s3)不是。
execution(
joke(String,…)))
指定切入點為:所有的 joke()方法,該方法第一個引數為 String,後面可以有任意個引數且
引數型別不限,如 joke(String s1)、joke(String s1,String s2)和 joke(String s1,double d2,String s3)
都是。
execution(* joke(Object))
指定切入點為:所有的 joke()方法,方法擁有一個引數,且引數是 Object 型別。joke(Object ob)
是,但,joke(String s)與 joke(User u)均不是。
execution(* joke(Object+)))
指定切入點為:所有的 joke()方法,方法擁有一個引數,且引數是 Object 型別或該類的子類。
不僅 joke(Object ob)是,joke(String s)和 joke(User u)也是。
3.5.4 AspectJ 的開發環境(掌握)
(1) 匯入三個 Jar 包
1> spring 框架 aop 的實現 jar 包: spring-aop.jar
2> aspectj 框架對 aop 的實現 jar 包: aspectjrt.jar , aspectjweaver.jar
Spring 對 AOP 的實現包在 Spring 框架的解壓包中:spring-aop-4.3.9.RELEASE.jar
在這裡插入圖片描述在這裡插入圖片描述(2) 引入 AOP 約束
在配置檔案頭部,要引入關於 aop 的約束。在 Spring 框架的解壓目錄中,
\docs\spring-framework-reference\html 下的 xsd-configuration.html 檔案中。
在這裡插入圖片描述在前面 Spring 實現 AOP 時,並未引入 AOP 的約束,而在 AspectJ 實現 AOP 時,才提出
要引入 AOP 的約束。說明,配置檔案中使用的 AOP 約束中的標籤,均是 AspectJ 框架使用的,
而非 Spring 框架本身在實現 AOP 時使用的。
AspectJ 對於 AOP 的實現有註解和配置檔案兩種方式,常用是註解方式。
3.5.5 AspectJ 基於註解的 AOP 實現(掌握)
AspectJ 提供了以註解方式對於 AOP 的實現。
(1) 實現步驟
A、Step1:定義業務介面與實現類
在這裡插入圖片描述在這裡插入圖片描述B、 Step2:定義切面 POJO 類
該類為一個 POJO 類,將作為切面出現。其中定義了若干普通方法,將作為不同的通知
方法。
在這裡插入圖片描述D、Step4:在 POJO 類的普通方法上新增通知註解
切面類是用於定義增強程式碼的,即用於定義增強目標類中目標方法的增強方法。這些增強方法使用不同的“通知”註解,會在不同的時間點完成織入。當然,對於增強程式碼,還要
通過 execution 表示式指定具體應用的目標類與目標方法,即切入點。
在這裡插入圖片描述F、 Step6:註冊 AspectJ 的自動代理
在定義好切面 Aspect 後,需要通知 Spring 容器,讓容器生成“目標類+ 切面”的代理
物件。這個代理是由容器自動生成的。只需要在 Spring 配置檔案中註冊一個基於 aspectj 的
自動代理生成器,其就會自動掃描到@Aspect 註解,並按通知型別與切入點,將其織入,並生成代理。
在這裡插入圖片描述aop:aspectj-autoproxy/的底層是由 AnnotationAwareAspectJAutoProxyCreator 實現的。
從其類名就可看出,是基於 AspectJ 的註解適配自動代理生成器。
其工作原理是,aop:aspectj-autoproxy/通過掃描找到@Aspect 定義的切面類,再由切面類根據切入點找到目標類的目標方法,再由通知型別找到切入的時間點。
在這裡插入圖片描述(2) @Before 前置通知-方法有 JoinPoint 引數
在目標方法執行之前執行。被註解為前置通知的方法,可以包含一個 JoinPoint 型別參
數。該型別的物件本身就是切入點表示式。通過該引數,可獲取切入點表示式、方法簽名、
目標物件等。
不光前置通知的方法,可以包含一個 JoinPoint 型別引數,所有的通知方法均可包含該
引數。
在這裡插入圖片描述(3) @AfterReturning 後置通知-註解有 returning 屬性
在目標方法執行之後執行。由於是目標方法之後執行,所以可以獲取到目標方法的返回值。該註解的 returning 屬性就是用於指定接收方法返回值的變數名的。所以,被註解為後
置通知的方法,除了可以包含 JoinPoint 引數外,還可以包含用於接收返回值的變數。該變
量最好為 Object 型別,因為目標方法的返回值可能是任何型別。
在這裡插入圖片描述(4) @Around 環繞通知-增強方法有 ProceedingJoinPoint 引數
在目標方法執行之前之後執行。被註解為環繞增強的方法要有返回值,Object 型別。並
且方法可以包含一個 ProceedingJoinPoint 型別的引數。介面 ProceedingJoinPoint 其有一個
proceed()方法,用於執行目標方法。若目標方法有返回值,則該方法的返回值就是目標方法
的返回值。最後,環繞增強方法將其返回值返回。該增強方法實際是攔截了目標方法的執行。

在這裡插入圖片描述

(5) @AfterThrowing 異常通知-註解中有 throwing 屬性
在目標方法丟擲異常後執行。該註解的 throwing 屬性用於指定所發生的異常類物件。
當然,被註解為異常通知的方法可以包含一個引數 Throwable,引數名稱為 throwing 指定的
名稱,表示發生的異常物件。
在這裡插入圖片描述在這裡插入圖片描述(7) @Pointcut 定義切入點
當較多的通知增強方法使用相同的 execution 切入點表示式時,編寫、維護均較為麻煩。
AspectJ 提供了@Pointcut 註解,用於定義 execution 切入點表示式。
其用法是,將@Pointcut 註解在一個方法之上,以後所有的 executeion 的 value 屬性值
均可使用該方法名作為切入點。代表的就是@Pointcut 定義的切入點。這個使用@Pointcute
註解的方法一般使用 private 的標識方法,即沒有實際作用的方法。
在這裡插入圖片描述