Spring的AOP
面向切面程式設計是對於面向物件的一種補充,是一種很先進的思想,技術實現倒不是很高深,關於 Spring 的 AOP 需要掌握的有以下這些。
AOP 並不是 Spring 框架獨有的,Spring 只是支援 AOP 程式設計的框架之一而已。不同的框架對 AOP 的支援各有特點,有些框架支援對方法及其引數做攔截,有些則只能對方法進行攔截。Spring 就是後者,它只支援對方法進行攔截的 AOP 。

_Spring的AOP.png
AOP概述
AOP 全稱是 Aspect Oriented Programming ,中文含義面向切面程式設計。
AOP 的概念
網上官方說法:
Spring 的 AOP 指的就是在程式執行時,動態的將程式碼切入到指定位置上的一種程式設計思想。
自己的理解:
Spring 會通過配置檔案是否進行了 AOP 相關的配置,來動態決定到底應該採用目標物件還是代理物件。代理物件是對原有目標物件的方法進行了增強,所以一旦採用了代理物件,就會出現和原有不一樣的效果。
為什麼要使用AOP
比如在 Spring 中,只要編寫一個切面類,然後進行相關配置,Spring 就會去建立代理物件。如此就實現了在不修改原有程式碼的情況下,達到了和修改原有程式碼相同的效果。Spring 中的 AOP 通常都是用來進行許可權校驗、日誌記錄、效能監控、事務控制等功能的。
AOP 的底層實現原理
底層實現無非就是,在通過依賴注入給 Bean 物件的屬性進行賦值的時候,根據配置來決定是注入原有目標物件還是代理物件。Spring 中生成代理物件使用的技術有兩種,分別是 JDK 動態代理,和 CGLib 動態代理。
JDK 動態代理
要求目標類必須實現介面,才能採用這種方式產生代理物件。
CGLib 動態代理
針對沒有實現介面的類產生代理,它是通過繼承的方式實現的代理。
AOP的相關術語
下面以 CustomerDao
類為例,來講解 AOP 相關術語。
//目標類 public class CustomerDao { public void save() { System.out.println("儲存客戶..."); } public void find() { System.out.println("查詢客戶..."); } public void update() { System.out.println("修改客戶..."); } public void delete() {織入 System.out.println("刪除客戶..."); } }
//切面類,下面是兩個方法就是通知 public class MyAspectXML { public void checkPri(JoinPoint jointPoint) { System.out.println("許可權校驗 "+jointPoint); } public void writeLog(Object result) { System.out.println("日誌記錄 "+result); } }
前面已經提到,Spring 支援的是基於方法的 AOP ,也就是說它只能對方法進行增強。

_AOP相關術語.png
相關術語:
-
連線點(Joinpoint)
,目標類的所有方法,因為所有方法都可能被增強 -
切入點(Pointcut)
,就是會被增強的方法 -
通知(Advice)
,就是切面類中由程式設計師新增的用於在切入點之前或之後執行的方法,比如許可權驗證方法和日誌記錄方法。 -
目標類(Target)
,就是原有目標類啊 -
代理類(Proxy)
,由 Spring 框架集合目標類、切面類和配置檔案生成的對切入點進行了增強的那個類 -
切面(Aspect)
,切入點和通知的組合,就是一個切面。比如save()
和writeLog()
兩個方法就組成了一個切面。 面向切面程式設計就是在原有的方法之前或之後新增方法,組成一個新的方法唄。 -
織入(Weaving)
,就是把通知應用到切入點的這個 過程 就叫做織入。也就是生成代理物件的過程唄!
通知的型別
通知就是切面類中的一個個方法,通知的型別指的是它和切入點的組成關係。

_通知的型別.png
-
前置通知
,在切入點之前執行的方法,比如許可權驗證方法。 -
後置通知
,在切入點之後執行的方法,比如日誌記錄方法。 -
環繞通知
,在切入點之後和之後都會執行的方法,比如記錄當前時間的方法,通常用於測試系統性能。 -
異常丟擲通知
,在切入點出現異常的時候會執行的方法,無非就是捕捉到異常後,先執行此方法,再丟擲異常而已。 -
最終通知
,無論切入點是否出現異常,都會執行該方法,其實就是把通知加在了finally
程式碼塊之內唄。
切入點表示式的寫法
如何通過配置指定哪些類的哪些方法需要進行增強呢?就是通過切入點表示式來實現的。
切入點表示式的格式是: 方法返回值
+ 包名.類名.方法名(方法的引數)
,其中除了引數之外,其他任何一個位置都可以用萬用字元 *
表示。方法的引數用 ..
表示。
實際使用範例如下:
<aop:pointcut expression="execution(* packageName.ProductDaoImpl.save(..))" id="pointcut1"/>
XML 配置AOP
配置 AOP 首先要明確幾個角色,分別是切面類,目標類,切入點、通知。以上面的目標類和切面類為例,他們的配置如下
<aop:config> <!-- 配置切入點 --> <aop:pointcut expression="execution(* com.itheima.spring.demo3.ProductDaoImpl.save(..))" id="pointcut1"/> <aop:pointcut expression="execution(* com.itheima.spring.demo3.ProductDaoImpl.delete(..))" id="pointcut2"/> <aop:pointcut expression="execution(* com.itheima.spring.demo3.ProductDaoImpl.update(..))" id="pointcut3"/> <aop:pointcut expression="execution(* com.itheima.spring.demo3.ProductDaoImpl.find(..))" id="pointcut4"/> <!-- 配置切面類,此標籤內可以配置多個切面 --> <aop:aspect ref="myAspect"> <!-- 前置通知--> <aop:before method="checkPri" pointcut-ref="pointcut1"/> <!-- 後置通知 --> <aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/> <!-- 環繞通知 --> <aop:around method="around" pointcut-ref="pointcut3"/> <!-- 異常丟擲通知 --> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/> <!-- 最終通知 --> <aop:after method="after" pointcut-ref="pointcut4"/> </aop:aspect> </aop:config>
AspectJ註解配置AOP
用 AspectJ 註解的方式配置 AOP ,其實主要是需要配置切面類和通知,還有切入點。
切面類的配置:
-
@Aspect
,用此註解給切面類進行配置。
@Aspect//表明是切面類 public class MyAspectAnno {}
通知的配置:
-
@Before
,配置前置通知
@Before(value="execution(* com.itheima.spring.demo1.OrderDao.save(..))") public void before() { System.out.println("save方法前置增強====="); }//其他通知同理
@AfterReturning @Around @AfterThrowing @After
切入點的配置:
-
@Pointcut
,
@Pointcut(value="execution(* packageName.ProductDaoImpl.save(..))") public void save() { System.out.println("儲存訂單..."); }