目錄
一、什么是Spring AOP
二、AOP的使用場景
三、AOP組成
四、Spring AOP的實現
1、添加Spring AOP依賴
2、定義切面和切點
3、定義相關通知
五、 AOP的實現原理
1、什么是動態代理?
2、 JDK代理和CGLIB代理的區別
一、什么是Spring AOP
AOP(Aspect Oriented Programming),直譯過來就是面向切面編程,AOP是一種編程思想,是面向對象編程(OOP)的一種補充。Spring AOP是AOP思想的一種實現,就像DI一樣是IoC的一種實現。
AOP的主要作用就是分離功能性需求和非功能性需求,使開發人員可以集中處理某一個關注點,減少對業務代碼的侵入。增強代碼的可讀性和可維護性。簡單來所,AOP的作用就是保證開發者在不修改業務代碼的前提下,位系統中的業務組件添加某種通用功能。
就比如實現一個用戶登錄權限的校驗功能,就比如我們使用的博客,在要進入博客編輯的頁面時,需要對你是否登錄進行校驗,如果已經登錄,那么就可以進入編輯頁,如果沒有那么就需要在登錄頁面登錄之后在進入。像這樣需要登錄校驗的頁面,我們使用AOP思想,只需要在某一處配置以下,所有的需要判斷用戶登錄的頁面就可以實現用戶登錄驗證了。這樣每個頁面就只關注具體的業務邏輯了。
二、AOP的使用場景
就像上面舉的例子,當你的程序中實現的頁面越來越多,那么你要 寫的登錄驗證也越來越多,?這些?法?是相同的,這么多的?法就會代碼修改和維護的成本我們對這種功能統一,并且使用地方較多的功能,就可以考慮使用AOP來統一處理。當然AOP可以使用的場景還有很多。
- 統一日志記錄
- 統一方法執行時間統計
- 統一的返回格式設置
- 統一的異常處理
- 事務的開啟和提交等
如果沒有使用AOP思想來寫代碼,用戶發送的請求直接被業務代碼控制層接收到,請求訪問的頁面來校驗用戶是否登錄。使用了AOP思想的代碼,用戶發送方的請求被AOP這里的代碼先進行登錄校驗,如果登錄,將請求傳給控制層,如果沒有登錄就會被攔截。
三、AOP組成
1??切面(Aspect):表示當前AOP是針對那些事件做處理的,用來登錄的還是記錄日志的。切面就是通知和切點的結合,通知和切點共同定義了切面的全部內容,他是干什么的,什么時候在哪里執行。通常以類的形式表示。
2??切點(Pointcut):表示定義具體規則。切點其實就是篩選出的連接點,一個類中的所有方法都是連接點,但又不全需要,會篩選出某些作為連接點作為切點,如果說通知定義了切面的動作后者執行時機的話,切點則定義了執行的地點。
3??通知(Advice):AOP執行的具體方法。有的地方叫增強。
4??連接點(Join point):就是有可能觸發切點的所有點。應用執行過程中能夠插入切面的一個點,這個點可以是方法調用時,異常拋出時,甚至修改字段時。切面代碼可以利用這些點插入到應用的正常流程之中,并添加新的行為。
四、Spring AOP的實現
1、添加Spring AOP依賴
在創建好的Spring Boot項目的pom.xml中添加Spring AOP的依賴,我們可以從中央倉庫中下載
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后點擊刷新,觸發下載。
2、定義切面和切點
這里使用注解@Aspect表示定義切面,即UserAserAspect類為切面,使用@Component注解表示讓切面隨著框架的啟動而啟動,這樣切面中的切點定義的攔截規則才能生效。
package com.example.demo.common;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect//定義切面
@Component//讓切面隨著框架的啟動而啟動
public class UserAspect {//定義切點,@Pointcut注解的參數中定義了具體的攔截規則。參數中使用AspectJ表達式語法@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}
}
上述代碼中,pointcut方法為空方法,它不需要又方法體,此方法名就是起到一個"標識"的作用,標識下面的通知方法具體指的是那個切點。因為一個切面中有很多切點。
上述pointcut方法上添加的@Pointcut注解的參數中使用切點表達式定義了具體的攔截規則。
execution(* com.example.demo.controller.UserController.*(..))
切點表達的意思是:攔截UserContrller類中的所有方法其參數為任意參數并且返回值為任意類型的返回值。
- execution表示的意思為執行,執行的是后面跟的()中的規則。
- *表示的多個部分組成的,有修飾符和返回值類型。
- com.example.demo.controller.UserController表示要攔截com.example.demo.controller包中的UserController類
- 類后面跟的*表示UserController類中的所有方法。
- ..表示的不定式傳參
切點表達式由切點函數組成,其中execution()是最常見的切點函數用來匹配方法,語法為:
execution(<修飾符><返回值類型><包.類.方法(參數)><異常>)
常見表達式示例
- execution(* com.example.demo.User.*(..)):匹配User類中的所有方法。
- execution(* com.example.demo.User+.*(..)):匹配該類的子類包括該類的所有方法
- execution(* com.example.*.*(..)):匹配com.example包下的所有類的所有方法
- execution(* com.example..*.*(..)):匹配com.example包下,子孫包下所有類的所有方法
- execution(* addUser(String,int)):匹配addUser方法,其第一個參數類型是String,第二個參數類型是int。
創建UserController類,這個類中的方法哪一個要被執行(目標方法)哪一個就是連接點
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getuser")public String getUser(){System.out.println("do getUser");return "get user";}@RequestMapping("/deluser")public String delUser(){System.out.println("do delUser");return "del user";}
}
3、定義相關通知
通知定義的是被攔截的方法具體要執行的業務。比如用戶登錄權限驗證方法就是具體要執行的業務。
Spring AOP中,可以在方法上使用以下注解,會設置方法為通知方法,在滿足條件后會通知本方法進行調用:
- 前置通知使用@Before:通知方法會在目標方法(連接點)調用之前執行
- 后置通知使用@After:通知方法會在目標方法(連接點)返回或者拋出異常后調用
- 返回之后通知使用@AfterReturning:通知方法會在目標方法(連接點)返回后調用
- 拋異常后通知使用@AfterThrowing:通知方法會在目標方法(連接點)拋出異常后調用
- 環繞通知使用@Around:通知包裹了被通知的方法,在被通知的方法之前和調用之后執行自定義的行為。
1??前置通知和后置通知的實現
package com.example.demo.common;import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect//定義切面
@Component//讓切面隨著框架的啟動而啟動
public class UserAspect {//定義切點,@Pointcut注解的參數中定義了具體的攔截規則@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}//定義前置通知@Before("pointcut()")//表示這個通知是針對pointcut方法的public void doBefore(){System.out.println("執行了前置通知");}//定義后置通知@After("pointcut()")public void doAfter(){System.out.println("執行了后置通知");}
}
當我們在前端頁面中訪問UserController類的方法時,后端程序的控制臺上每次出現的結果是先執行前置通知,在執行目標方法(連接點),然后執行后置通知。
?
2??環繞通知的具體實現?
環繞通知方法是具有Object類型的返回值,需要把方法執行結果返回給框架,框架拿到對象繼續執行。
package com.example.demo.common;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect//定義切面
@Component//讓切面隨著框架的啟動而啟動
public class UserAspect {//定義切點,@Pointcut注解的參數中定義了具體的攔截規則@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}//定義前置通知@Before("pointcut()")//表示這個通知是針對pointcut方法的public void doBefore(){System.out.println("執行了前置通知");}//定義后置通知@After("pointcut()")public void doAfter(){System.out.println("執行了后置通知");}//定義環繞通知@Around("pointcut()")//環繞通知方法的參數為要執行的連接點,也就是我們在前端訪問的目標方法public Object doAround(ProceedingJoinPoint joinPoint){System.out.println("環繞通知之前");Object result = null;try {//執行目標方法,它的目標方法就是我們在前端訪問的方法result = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("環繞通知之后");return result;}
}
從執行結果中可以看到環繞通知的執行范圍,可以環繞執行通知是最先執行的,然后是執行前置通知,然后再執行目標方法,然后執行后置通知,最后所有的方法執行完成了,環繞通知方法才會執行完成。
?
五、 AOP的實現原理
Spring AOP是建立再動態代理的基礎上的,Spring對AOP的支持局限于方法級別的攔截。
Spring AOP使用兩種混合的實現方式:JDK動態代理和CGLib動態代理。
- JDK動態代理:如果目標對象實現了InvocationHandler接口,Spring將使用JDK動態代理來創建代理對象。
- CGLib動態代理:如果目標對象沒有實現InvocationHandler接口,Spring將使用CGLib代理,通過繼承目標對象來創建代理對象。
1、什么是動態代理?
代理可以看作是對調用目標的一個包裝,這樣我們對目標代理的調用不是直接發生的,而是通過代理完成。
當想要給實現了某個接口的類中的方法,加一些額外的處理,比如加日志,加事務等。可以給這個類創建一個代理,也就是創建一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新方法,這個代理類并不是定義好的,而是動態生成的,具有解耦意義,靈活、擴展性強。
在Java中,動態代理通常使用Java.lang.reflect.Proxy類和Java.lang.reflect.InvocationHandler接口來實現。
2、 JDK代理和CGLIB代理的區別
- 接口要求:JDK動態代理只能對實現了接口的類生成代理;而CGLIB代理可以沒有實現接口的類,是通過繼承被代理類,在運行時動態的生成代理對象。
- 生成方式:JDK代理使用Java的反射機制來完成代理對象,而CGLIB代理使用CGLIB庫生成代理對象,通過修改目標類的字節碼來實現。所以該類不能被final修飾
如果代理類沒有實現 InvocationHandler 接口,那么Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態的生成指定類的一個子類對象,并覆蓋其中特定方法并添加增強代碼,從而實現AOP。CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態代理的。
?