Spring Aop 掃盲
關(guān)于AOP
面向切面編程(Aspect-oriented Programming,俗稱AOP)提供了一種面向?qū)ο缶幊?Object-oriented Programming,俗稱OOP)的補(bǔ)充,面向?qū)ο缶幊套詈诵牡膯卧穷?lèi)(class),然而面向切面編程最核心的單元是切面(Aspects)。與面向?qū)ο蟮捻樞蛄鞒滩煌珹OP采用的是橫向切面的方式,注入與主業(yè)務(wù)流程無(wú)關(guān)的功能,例如事務(wù)管理和日志管理。

Spring的一個(gè)關(guān)鍵組件是AOP框架。雖然Spring IoC容器不依賴于AOP(意味著你不需要在IOC中依賴AOP),但AOP為Spring IoC提供了非常強(qiáng)大的中間件解決方案。
AOP 是一種編程范式,最早由 AOP 聯(lián)盟的組織提出的,通過(guò)預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。它是 OOP的延續(xù)。利用 AOP 可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開(kāi)發(fā)的效率
我們之間的開(kāi)發(fā)流程都是使用順序流程,那么使用 AOP 之后,你就可以橫向抽取重復(fù)代碼,什么叫橫向抽取呢?或許下面這幅圖你能理解,先來(lái)看一下傳統(tǒng)的軟件開(kāi)發(fā)存在什么樣風(fēng)險(xiǎn)。
縱向繼承體系:

在改進(jìn)方案之前,我們或許都遇到過(guò) IDEA 對(duì)你輸出 Duplicate Code 的時(shí)候,這個(gè)時(shí)候的類(lèi)的設(shè)計(jì)是很糟糕的,代碼寫(xiě)的也很冗余,基本上 if...else... 完成所有事情,這個(gè)時(shí)候就需要把相同的代碼抽取出來(lái)成為公共的方法,降低耦合性。這種提取代碼的方式是縱向抽取,縱向抽取的代碼之間的關(guān)聯(lián)關(guān)系非常密切。
橫向抽取:

橫向抽取也是代碼提取的一種方式,不過(guò)這種方式不會(huì)修改主要業(yè)務(wù)邏輯代碼,只是在此基礎(chǔ)上添加一些與主要的業(yè)務(wù)邏輯無(wú)關(guān)的功能,AOP 采取橫向抽取機(jī)制,補(bǔ)充了傳統(tǒng)縱向繼承體系(OOP)無(wú)法解決的重復(fù)性 代碼優(yōu)化(性能監(jiān)視、事務(wù)管理、安全檢查、緩存),將業(yè)務(wù)邏輯和系統(tǒng)處理的代碼(關(guān)閉連接、事務(wù)管理、操作日志記錄)解耦。
AOP 的概念
在深入學(xué)習(xí)SpringAOP 之前,讓我們先對(duì)AOP的幾個(gè)基本術(shù)語(yǔ)有個(gè)大致的概念,這些概念不是很容易理解,比較抽象,可以知道有這么幾個(gè)概念,下面一起來(lái)看一下:
切面(Aspect):Aspect 聲明類(lèi)似于 Java 中的類(lèi)聲明,事務(wù)管理是AOP一個(gè)最典型的應(yīng)用。在AOP中,切面一般使用@Aspect注解來(lái)使用,在XML 中,可以使用來(lái)定義一個(gè)切面。連接點(diǎn)(Join Point): 一個(gè)在程序執(zhí)行期間的某一個(gè)操作,就像是執(zhí)行一個(gè)方法或者處理一個(gè)異常。在Spring AOP中,一個(gè)連接點(diǎn)就代表了一個(gè)方法的執(zhí)行。通知(Advice):在切面中(類(lèi))的某個(gè)連接點(diǎn)(方法出)采取的動(dòng)作,會(huì)有四種不同的通知方式:around(環(huán)繞通知),before(前置通知),after(后置通知), exception(異常通知),return(返回通知)。許多AOP框架(包括Spring)將建議把通知作為為攔截器,并在連接點(diǎn)周?chē)S護(hù)一系列攔截器。切入點(diǎn)(Pointcut):表示一組連接點(diǎn),通知與切入點(diǎn)表達(dá)式有關(guān),并在切入點(diǎn)匹配的任何連接點(diǎn)處運(yùn)行(例如執(zhí)行具有特定名稱的方法)。由切入點(diǎn)表達(dá)式匹配的連接點(diǎn)的概念是AOP的核心,Spring默認(rèn)使用AspectJ切入點(diǎn)表達(dá)式語(yǔ)言。介紹(Introduction):introduction可以為原有的對(duì)象增加新的屬性和方法。例如,你可以使用introduction使bean實(shí)現(xiàn)IsModified接口,以簡(jiǎn)化緩存。目標(biāo)對(duì)象(Target Object):由一個(gè)或者多個(gè)切面代理的對(duì)象。也被稱為"切面對(duì)象"。由于Spring AOP是使用運(yùn)行時(shí)代理實(shí)現(xiàn)的,因此該對(duì)象始終是代理對(duì)象。AOP代理(AOP proxy):由AOP框架創(chuàng)建的對(duì)象,在Spring框架中,AOP代理對(duì)象有兩種:JDK動(dòng)態(tài)代理和CGLIB代理織入(Weaving):是指把增強(qiáng)應(yīng)用到目標(biāo)對(duì)象來(lái)創(chuàng)建新的代理對(duì)象的過(guò)程,它(例如 AspectJ 編譯器)可以在編譯時(shí)期,加載時(shí)期或者運(yùn)行時(shí)期完成。與其他純Java AOP框架一樣,Spring AOP在運(yùn)行時(shí)進(jìn)行織入。
Spring AOP 中通知的分類(lèi)
前置通知(Before Advice): 在目標(biāo)方法被調(diào)用前調(diào)用通知功能;相關(guān)的類(lèi)
org.springframework.aop.MethodBeforeAdvice后置通知(After Advice): 在目標(biāo)方法被調(diào)用之后調(diào)用通知功能;相關(guān)的類(lèi)
org.springframework.aop.AfterReturningAdvice返回通知(After-returning): 在目標(biāo)方法成功執(zhí)行之后調(diào)用通知功能;
異常通知(After-throwing): 在目標(biāo)方法拋出異常之后調(diào)用通知功能;相關(guān)的類(lèi)
org.springframework.aop.ThrowsAdvice環(huán)繞通知(Around): 把整個(gè)目標(biāo)方法包裹起來(lái),在被調(diào)用前和調(diào)用之后分別調(diào)用通知功能相關(guān)的類(lèi)
org.aopalliance.intercept.MethodInterceptor
Spring AOP 中織入的三種時(shí)期
編譯期:切面在目標(biāo)類(lèi)編譯時(shí)被織入,這種方式需要特殊的編譯器。AspectJ 的織入編譯器就是以這種方式織入切面的。類(lèi)加載期:切面在目標(biāo)類(lèi)加載到 JVM 時(shí)被織入,這種方式需要特殊的類(lèi)加載器( ClassLoader ),它可以在目標(biāo)類(lèi)引入應(yīng)用之前增強(qiáng)目標(biāo)類(lèi)的字節(jié)碼。運(yùn)行期:切面在應(yīng)用運(yùn)行的某個(gè)時(shí)期被織入。一般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象,Spring AOP 采用的就是這種織入方式。
AOP 的兩種實(shí)現(xiàn)方式
AOP 采用了兩種實(shí)現(xiàn)方式:靜態(tài)織入(AspectJ 實(shí)現(xiàn))和動(dòng)態(tài)代理(Spring AOP實(shí)現(xiàn))
AspectJ
AspectJ 是一個(gè)采用Java 實(shí)現(xiàn)的AOP框架,它能夠?qū)Υa進(jìn)行編譯(一般在編譯期進(jìn)行),讓代碼具有AspectJ 的 AOP 功能,AspectJ 是目前實(shí)現(xiàn) AOP 框架中最成熟,功能最豐富的語(yǔ)言。ApectJ 主要采用的是編譯期靜態(tài)織入的方式。在這個(gè)期間使用 AspectJ 的 acj 編譯器(類(lèi)似 javac)把 aspect 類(lèi)編譯成 class 字節(jié)碼后,在 java 目標(biāo)類(lèi)編譯時(shí)織入,即先編譯 aspect 類(lèi)再編譯目標(biāo)類(lèi)。

Spring AOP 實(shí)現(xiàn)
Spring AOP 是通過(guò)動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)的,而動(dòng)態(tài)代理是基于反射設(shè)計(jì)的。Spring AOP 采用了兩種混合的實(shí)現(xiàn)方式:JDK 動(dòng)態(tài)代理和 CGLib 動(dòng)態(tài)代理,分別來(lái)理解一下

JDK動(dòng)態(tài)代理:Spring AOP的首選方法。每當(dāng)目標(biāo)對(duì)象實(shí)現(xiàn)一個(gè)接口時(shí),就會(huì)使用JDK動(dòng)態(tài)代理。目標(biāo)對(duì)象必須實(shí)現(xiàn)接口
CGLIB代理:如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口,則可以使用CGLIB代理。
Spring 對(duì) AOP的支持
Spring 提供了兩種AOP 的實(shí)現(xiàn):基于注解式配置和基于XML配置
@AspectJ 支持
為了在Spring 配置中使用@AspectJ ,你需要啟用Spring支持,以根據(jù)@AspectJ切面配置Spring AOP,并配置自動(dòng)代理。自動(dòng)代理意味著,Spring 會(huì)根據(jù)自動(dòng)代理為 Bean 生成代理來(lái)攔截方法的調(diào)用,并確保根據(jù)需要執(zhí)行攔截。
可以使用XML或Java樣式配置啟用@AspectJ支持。在任何一種情況下,都還需要確保AspectJ的aspectjweaver.jar 第三方庫(kù)位于應(yīng)用程序的類(lèi)路徑中(版本1.8或更高版本)。
開(kāi)啟@AspectJ 支持
使用@Configuration 支持@AspectJ 的時(shí)候,需要添加 @EnableAspectJAutoProxy 注解,就像下面例子展示的這樣來(lái)開(kāi)啟 AOP代理
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {}
也可以使用XML配置來(lái)開(kāi)啟@AspectJ 支持
<aop:aspectj-autoproxy/>
默認(rèn)你已經(jīng)添加了 aop 的schema 空間,如果沒(méi)有的話,你需要手動(dòng)添加
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
beans>
聲明一個(gè)切面
在啟用了@AspectJ支持的情況下,在應(yīng)用程序上下文中定義的任何bean都具有@AspectJ方面的類(lèi)(具有@Aspect注釋?zhuān)琒pring會(huì)自動(dòng)檢測(cè)并用于配置Spring AOP。
使用XML 配置的方式定義一個(gè)切面
<aop:aspect />
使用注解的方式定義一個(gè)切面
@Aspect
public class MyAspect {}
切面(也就是用@Aspect注解的類(lèi))就像其他類(lèi)一樣有屬性和方法。它們能夠包含切入點(diǎn),通知和介紹聲明。
通過(guò)自動(dòng)掃描檢測(cè)切面
你可以在Spring XML 配置中將切面類(lèi)注冊(cè)為常規(guī)的bean,或者通過(guò)類(lèi)路徑掃描自動(dòng)檢測(cè)它們 - 與任何其他Spring管理的bean相同。然而,只是注解了@Aspect 的類(lèi)不會(huì)被當(dāng)作bean 進(jìn)行管理,你還需要在類(lèi)上面添加 @Component 注解,把它當(dāng)作一個(gè)組件交給 Spring 管理。
定義一個(gè)切點(diǎn)
一個(gè)切點(diǎn)由兩部分組成:包含名稱和任何參數(shù)以及切入點(diǎn)表達(dá)式的簽名,該表達(dá)式能夠確定我們想要執(zhí)行的方法。在@AspectJ注釋風(fēng)格的AOP中,切入點(diǎn)表達(dá)式需要用@Pointcut注解標(biāo)注(這個(gè)表達(dá)式作為方法的簽名,它的返回值必須是 void)。
@Pointcut("execution(* transfer(..))") // 切入點(diǎn)表達(dá)式
private void definePointcut() {}// 方法簽名
切入點(diǎn)表達(dá)式的編寫(xiě)規(guī)則如下:

現(xiàn)在假設(shè)我們需要配置的切點(diǎn)僅僅匹配指定的包,就可以使用 within() 限定符來(lái)表示,如下表達(dá)式所述:

請(qǐng)注意我們使用了 && 操作符把 execution() 和 within() 指示器連接在一起,表示的是 和 的關(guān)系,類(lèi)似的,你還可以使用 || 操作來(lái)表示 或 的關(guān)系, 使用 ! 表示 非 的關(guān)系。
除了within() 表示的限定符外,還有其它的限定符,下面是一個(gè)限定符表
| AspectJ 描述符 | 描述 |
|---|---|
| arg() | 限制連接點(diǎn)匹配參數(shù)為指定類(lèi)型的執(zhí)行方法 |
| @args() | 限制連接點(diǎn)匹配參數(shù)由指定注解標(biāo)注的執(zhí)行方法 |
| execution() | 用于匹配是連接點(diǎn)的執(zhí)行方法 |
| this() | 限制連接點(diǎn)匹配的AOP代理的bean引用為指定類(lèi)型的類(lèi) |
| target | 限制連接點(diǎn)匹配目標(biāo)對(duì)象為指定類(lèi)型的類(lèi) |
| @target() | 限制連接點(diǎn)匹配特定的執(zhí)行對(duì)象,這些對(duì)象對(duì)應(yīng)的類(lèi)要具有指定類(lèi)型的注解 |
| within() | 限制連接點(diǎn)匹配指定的類(lèi)型 |
| @within() | 限制連接點(diǎn)匹配指定注解所標(biāo)注的類(lèi)型 |
| @annotationn | 限定匹配帶有指定注解的連接點(diǎn) |
使用XML配置來(lái)配置切點(diǎn)
<aop:config>
<aop:aspect ref = "">
<aop:poincut id = "" expression="execution(** com.cxuan.aop.definePointcut(......))"/>
aop:aspect>
aop:config>
聲明一個(gè)通知
通知是和切入點(diǎn)表達(dá)式相互關(guān)聯(lián),用于在方法執(zhí)行之前,之后或者方法前后,方法返回,方法拋出異常時(shí)調(diào)用通知的方法,切入點(diǎn)表達(dá)式可以是對(duì)命名切入點(diǎn)的簡(jiǎn)單引用,也可以是在適當(dāng)位置聲明的切入點(diǎn)表達(dá)式。下面以一個(gè)例子來(lái)演示一下這些通知都是如何定義的:
上面的例子就很清晰了,定義了一個(gè) Audience 切面,并在切面中定義了一個(gè)performance() 的切點(diǎn),下面各自定義了表演之前、表演之后返回、表演失敗的時(shí)候進(jìn)行通知,除此之外,你還需要在main 方法中開(kāi)啟 @EnableAspectJAutoProxy 來(lái)開(kāi)啟自動(dòng)代理。
除了使用Java Config 的方式外,你還可以使用基于XML的配置方式

當(dāng)然,這種切點(diǎn)定義的比較冗余,為了解決這種類(lèi)似 if...else... 災(zāi)難性的業(yè)務(wù)邏輯,你需要單獨(dú)定義一個(gè),然后使用 pointcut-ref 屬性指向上面那個(gè)標(biāo)簽,就像下面這樣

環(huán)繞通知
在目標(biāo)方法執(zhí)行之前和之后都可以執(zhí)行額外代碼的通知。在環(huán)繞通知中必須顯式的調(diào)用目標(biāo)方法,目標(biāo)方法才會(huì)執(zhí)行,這個(gè)顯式調(diào)用時(shí)通過(guò)ProceedingJoinPoint來(lái)實(shí)現(xiàn)的,可以在環(huán)繞通知中接收一個(gè)此類(lèi)型的形參,spring容器會(huì)自動(dòng)將該對(duì)象傳入,注意這個(gè)參數(shù)必須處在環(huán)繞通知的第一個(gè)形參位置。
環(huán)繞通知需要返回返回值,否則真正調(diào)用者將拿不到返回值,只能得到一個(gè)null。下面是環(huán)繞通知的一個(gè)示例
<aop:around method="around" pointcut-ref="pc1"/>
public Object around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("1 -- around before...");
Object obj = jp.proceed(); //--顯式的調(diào)用目標(biāo)方法
System.out.println("1 -- around after...");
return obj;
}戳:百萬(wàn)字長(zhǎng)文帶你學(xué)習(xí)「Java」
如果大家想要實(shí)時(shí)關(guān)注我更新的文章以及分享的干貨的話,可以關(guān)注我的公眾號(hào)Java3y。
獲取Java精美腦圖

?獲取Java學(xué)習(xí)路線

獲取開(kāi)發(fā)常用工具

?加入技術(shù)交流群

在公眾號(hào)下回復(fù)「888」即可獲取!!

點(diǎn)個(gè)在看
,分享到朋友圈
,對(duì)我真的很重要!!
