AOP的定義:AOP(Aspect Oriented Progamming)利用稱為"橫切"的技術,剖解開封裝的物件內部,把多個類的公共行為封裝到一個可重用模組中,便於減少重複程式碼,降低模組之間的耦合度,符合「開閉原則」。
上面這段關於AOP的定義是從網上抄的,是不是很拗口,我們就結合實際開發來先簡單瞭解下AOP中的一些專業術語,然後再去學習使用它吧。
在通常的業務開發中,我們往往只需要讓業務邏輯去關注業務本身,但是一些複雜的業務我們為了保證資料的完整、後序發生問題之後能夠快速定位問題,通常伴隨著安全、紀錄檔、事務等程式碼,業務寫多了我們發現這些程式碼通常都是類似的,那麼我們不就可以將這些類似的程式碼封裝起來,再需要使用的地方進行復用嗎?AOP就為提供了這麼一種思路,但是AOP中的一些專業術語特別拗口,我們在這簡單總結下,做下記錄方便後面的學習。
1、通知(Advice):就是我們上面提到的除了業務程式碼本身之外,一些「可重用的一些程式碼」,先定義好,在需要的地方進行重用;
2、連線點(JoinPoint):允許你使用「通知」的地方,就是允許你重複使用程式碼的地方,方法執行的前後或者方法丟擲異常時,Spring只支援方法級別的連線點;
3、切入點(Pointcut): 假如有若干方法,但是我們並不是想在所有的方法中「重用這段可複用的程式碼」,我們只想在某些特定的方法上使用通知,那麼我們就可以使用切入點來進行這些連線點的「篩選了」;
4、切面(Aspect):切面簡單來講就是切入點(JoinPoint)和通知(Advice)的結合體。通知(Advice)決定了要幹什麼(通知的方法體)?在什麼時候幹?(定義通知的註解型別)。而切入點決定了要在哪兒幹(即執行通知定義的方法體)。
5、引入(Introduction):允許我們向目標物件新增新的方法屬性(即通過執行通知來控制對目標方法的存取);
6、目標物件(Target):引入中所提到的目標物件,也就是要被通知的物件,也就是執行真正的業務邏輯,可以在毫不知情的情況下,織入我們的切面;
7、代理(Proxy):Spring中的AOP都是通過動態代理來實現的,關於代理有不明白的可以參考設計模式之(8)——代理模式;
8、織入(Weaving):把切面應用到目標物件,建立代理物件的過程;
9、AOP方法:通知+目標物件的方法。
上面就是AOP程式設計中一些常用的屬於,然後我們再看看AOP中最重要的通知(Advice)的分類,通知分為五類:
為了方便下面理解,我們可以認為「連線點就是一個目標方法」。
1、前置通知(Before Advice):在目標方法之前執行,前置通知不會影響目標方法的執行,除非前置通知丟擲異常;
2、正常返回通知(After Advice):在目標方法正常執行完成之後執行,如果目標方法丟擲異常,則不會執行;
3、異常返回通知(AfterThrowing Advice):在目標方法丟擲異常之後會執行;
4、返回通知(AfterReturning Advice):在目標方法執行完之後執行,不管目標方法是正常執行完,還是因為丟擲異常退出,都會執行返回通知內的內容;
5、環繞通知(Around Advice):環繞通知圍繞在目標方法執行的前後,是一個功能最為強大的通知型別,可以在方法執行前後自定義一些操作,環繞通知還需要負責決定是繼續處理join point(呼叫ProceedingJoinPoint的proceed方法)還是中斷執行,為了避免後面的空指標,環繞通知還需要返回方法的執行結果。
以下是我寫的一個簡單範例,通過一個自定義註解,來向需要的地方織入通知;
自定義註解:
package com.pep.process.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @ClassName: Audit * @Description: 描述這個類的作用 * @author: wwh * @date: 2023年3月2日 上午9:47:52 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface Audit { String action() default ""; String name() default ""; }
定義一個切面,切面中包含切入點和通知:
package com.pep.process.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @ClassName: AuditAop * @Description: 自定義切面 * @author: wwh * @date: 2023年3月2日 上午9:54:48 */ @Component @Aspect public class AuditAspect { /** * @Title: audit * @Description: 定義一個切入點 * void 返回型別 */ @Pointcut("@annotation(com.pep.process.annotation.Audit)") private void audit() { } /** * @Title: before * @Description: 前置通知 * void 返回型別 */ @Before("audit()") public void before() { System.err.println("我是前置通知..."); } /** * @Title: around * @Description: 環繞通知 * @param joinPoint * @return * Object 返回型別 */ @Around("audit()") public Object around(ProceedingJoinPoint joinPoint) { try { System.err.println("環繞通知執行前..."); Object proceed = joinPoint.proceed(); System.err.println("環繞通知執行後..."); return proceed; } catch (Throwable e) { e.printStackTrace(); System.err.println("目標方法執行過程中發生了異常..."); return null; } } /** * @Title: after * @Description: 描述這個方法的作用 * void 返回型別 */ @After(value="audit()") public void after() { System.err.println("我是後置通知..."); } /** * @Title: afterThrow * @Description: 返回異常通知 * @param ex * void 返回型別 */ @AfterThrowing(value="audit()",throwing="ex") public void afterThrow(Exception ex) { System.err.println("異常通知..."); } /** * @Title: afterReturning * @Description: 返回通知 * @param obj * @return * Object 返回型別 */ @AfterReturning(value="audit()",returning="obj") public Object afterReturning(Object obj) { System.err.println("我是返回通知..."); return obj; } }
需要注意的是異常通知的註解中的throwing引數,通過這個引數,可以目標方法執行過程中丟擲的異常,繫結到異常通知的方法引數中,並且這個引數值要和方法引數名稱一致。
同理返回通知註解中的returning引數,也是將目標方法的執行結果和返回通知中方法的引數進行繫結,名稱也必須一致。
通過使用自定義的方式來向需要的地方織入通知,實現方法功能「增強」或者控制方法存取。
package com.pep.process.controller; import net.sf.json.JSONObject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.pep.jxw.common.util.UUIDGenerator; import com.pep.process.annotation.Audit; @Controller public class LogController { @RequestMapping("/test.do") @Audit @ResponseBody public JSONObject log() { JSONObject json = new JSONObject(); json.put("id", UUIDGenerator.getUUID()); System.err.println("執行了目標方法..."); return json; } }
輸出結果如下:
相信通過上面這個返回結果的分析,我們對各種通知的執行順序有一個簡單瞭解,後續的一些問題我們有空再議。
參考文章:
本文來自部落格園,作者:一隻烤鴨朝北走,僅用於技術學習,所有資源都來源於網路,部分是轉發,部分是個人總結。歡迎共同學習和轉載,轉載請在醒目位置標明原文。如有侵權,請留言告知,及時撤除。轉載請註明原文連結:https://www.cnblogs.com/wha6239/p/17172501.html