二、AOP面向切面編程
官方下載地址:動力節點官網
視頻觀看地址
https://www.bilibili.com/video/BV1nz4y1d7uy
2.1 概述
AOP(Aspect Orient Programming),面向切面編程是從動態角度考慮程式運行程序
- AOP 底層,就是采用動態代理模式實作的,采用了兩種代理:JDK 的動態代理,與 CGLIB的動態代理,AOP就是動態代理的規范化, 把動態代理的實作步驟,方式都定義好了, 讓開發人員用一種統一的方式,使用動態代理
- Aspect: 切面,給你的目標類增加的功能,就是切面, 像上面用的日志,事務都是切面,切面的特點:一般都是非業務方法,獨立使用的
- Orient:面向, 對著
- Programming:編程
2.2 相關術語
1. Aspect:切面,表示增強的功能, 就是一堆代碼,完成某個一個功能,非業務功能,常見的切面功能有日志, 事務, 統計資訊, 引數檢查, 權限驗證,切面用于組織多個Advice,Advice放在切面中定義,實際就是對主業務邏輯的一種增強
2. JoinPoint:連接點 ,連接業務方法和切面的位置,就某類中的業務方法,程式執行程序中明確的點,如方法的呼叫,或者例外的拋出,在Spring AOP中,連接點總是方法的呼叫
3. Pointcut:切入點 ,指多個連接點方法的集合,多個方法,可以插入增強處理的連接點,簡而言之,當某個連接點滿足指定要求時,該連接點將被添加增強處理,該連接點也就變成了切入點
4. Advice:AOP框架在特定的切入點執行的增強處理,處理有"around"、"before"和"after"等型別,能表示切面功能執行的時間,切入點定義切入的位置,通知定義切入的時間
5. Target:目標物件,目 標 對 象 指 將 要 被 增 強 的 對 象 , 即 包 含 主 業 務 邏 輯 的 類 的 對 象
2.3 AspectJ
2.3.1 概述
AspectJ是一個基于Java語言的AOP框架,提供了強大的AOP功能,其主要包括兩個部分:
- 一個部分定義了如何表達、定義AOP編程中的語法規范;
- 另一個部分是工具部分,包括編譯、除錯工具等
aspectJ框架實作aop的兩種方式:
- 使用xml的組態檔 : 配置全域事務
- 使用注解,我們在專案中要做aop功能,一般都使用注解,aspectj有5個注解
- @Before
- @AfterReturning
- @Around
- @AfterThrowing
- @After
2.3.2 AspectJ的切入點運算式
運算式原型:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
相關解釋:
- modifiers-pattern? 訪問權限型別
- ret-type-pattern 回傳值型別
- declaring-type-pattern? 包名類名
- name-pattern(param-pattern) 方法名(引數型別和引數個數)
- throws-pattern 拋出例外型別
- ?表示可選的部分
以上運算式一共4個部分
execution(訪問權限 方法回傳值 方法宣告(引數) 例外型別)
| 符號 | 意義 |
|---|---|
| * | 0至多個任意字符 |
| . . | 用在方法引數中,表示任意多個引數;用在包名后,表示當前包與子包路徑 |
| + | 用在類名后,表示當前類及其子類;用在介面后,表示當前介面及其實作類 |
相關實體:
- execution(public * *(..)):任意公共方法
- execution(* set*(..)):任何一個以“set”開始的方法
- execution(* com.xyz.service.*.*(..)):定義在 service 包里的任意類的任意方法
- execution(* com.xyz.service..*.*(..)):定義在 service 包或者子包里的任意類的任意方法,“..”出現在類名中時,后面必須跟“*”,表示包、子包下的所有類
- execution(* *..service.*.*(..)):指定所有包下的 serivce 子包下所有類(介面)中所有方法為切入點
- execution(* com.xyz.service.IAccountService+.*(..)):IAccountService 若為介面,則為介面中的任意方法及其所有實作類中的任意方法;若為類,則為該類及其子類中的任意方法
- execution(* joke(String,int))):所有的 joke(String,int)方法,且 joke()方法的第一個引數是 String,第二個引數是 int;如果方法中的引數型別是 java.lang 包下的類,可以直接使用類名,否則必須使用全限定類名,如 joke( java.util.List, int)
2.3.3 前置通知:@Before
1.方法的定義要求:
- 公共方法 public
- 方法沒有回傳值
- 方法名稱自定義
- 方法可以有引數,也可以沒有引數
2.@Before: 前置通知注解
屬性:value ,是切入點運算式,表示切面的功能執行的位置
位置:在方法的上面
1.配置依賴
<!--spring依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--aspectj依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2.創建業務介面與實作類物件
Service.interface public interface Service { public void doSome(); } 點擊并拖拽以移動 ServiceImpl.java @org.springframework.stereotype.Service("myService") public class ServiceImpl implements Service { @Override public void doSome() { System.out.println("這是我的業務方法!!!"); } }
3.創建切面類:MyAspect.java
@Aspect @Component("myAspect") public class MyAspect { /** *指定通知方法中的引數:JoinPoint * */ @Before(value = "execution(void *..doSome(..))") public void before(){ System.out.println("這是前置通知"); } }
4.配置applicationContext.xml檔案
<!--掃描檔案-->
<context:component-scan base-package="com.jjh.*"/>
<!--宣告自動代理生成器-->
<aop:aspectj-autoproxy/>
5.測驗類呼叫
@org.junit.Test public void demo01(){ String config = "applicationContext.xml"; ApplicationContext app = new ClassPathXmlApplicationContext(config); Service proxy = (Service) app.getBean("myService"); //輸出當前類的資訊:com.sun.proxy.$Proxy17 //證明其使用的是JDK動態代理 System.out.println(proxy.getClass().getName()); proxy.doSome(); }
2.3.4 JoinPoint
- 指定通知方法中的引數 : JoinPoint
- JoinPoint:業務方法,要加入切面功能的業務方法
- 作用:可以在通知方法中獲取方法執行時的資訊, 例如方法名稱,方法的實參
- 如果需要切面功能中方法的資訊,就加入JoinPoint
- JoinPoint引數的值是由框架賦予, 必須是第一個位置的引數
- 不止前置通知的方法,可以包含一個 JoinPoint 型別引數,所有的通知方法均可包含該引數
MyAspect.java
@Aspect @Component("myAspect") public class MyAspect { @Before(value = "execution(void *..doSome(..))") public void before(JoinPoint joinPoint){ //獲取方法的定義 System.out.println("方法的簽名(定義):" + joinPoint.getSignature()); System.out.println("方法的名稱:" + joinPoint.getSignature().getName()); //獲取方法的實參 Object[] args = joinPoint.getArgs(); for (Object arg : args) { System.out.println("引數:" + arg); } System.out.println("這是前置通知!!!"); } }
測驗類
@org.junit.Test public void demo01(){ String config = "applicationContext.xml"; ApplicationContext app = new ClassPathXmlApplicationContext(config); Service proxy = (Service) app.getBean("myService"); proxy.doSome("張三",20); }
列印結果
2.3.5 后置通知:@AfterReturning
- 在目標方法執行之后執行
- 由于是目標方法之后執行,所以可以獲取到目標方法的回傳值
- 該注解的returning屬性就是用于指定接收方法回傳值的變數名的
- 所以,被注解為后置通知的方法,除了可以包含 JoinPoint 引數外,還可以包含用于接識訓傳值的變數
- 該變數最好為 Object 型別,因為目標方法的回傳值可能是任何型別
業務方法
@Component("myService2")
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("這是業務方法!!!");
}
@Override
public String doOther(String str, int i) {
return "業務方法doOther的回傳值!!!";
}
}
后置通知
1.該注解的 returning 屬性就是用于指定接收方法回傳值的變數名的
2.除了可以包含 JoinPoint 引數外,還可以包含用于接識訓傳值的變數
3.該變數最好為Object 型別,因為目標方法的回傳值可能是任何型別
4.方法的定義要求:
- 公共方法 public
- 方法沒有回傳值
- 方法名稱自定義
- 方法有引數的,推薦是Object ,引數名自定義
5.@AfterReturning:后置通知
- value:切入點運算式
- returning:自定義的變數,表示目標方法的回傳值的,自定義變數名必須和通知方法的形參名一樣
- 可以根據業務方法的回傳值做出相應的操作
@AfterReturning(value = "https://www.cnblogs.com/laoduyyds/p/execution(String *..doOther(..))",returning = "res") public void myAfterReturning(JoinPoint joinPoint,Object res){ //獲取方法的簽屬(定義) System.out.println(joinPoint.getSignature()); //獲取方法的引數 Object[] args = joinPoint.getArgs(); for (Object arg : args) { System.out.println("目標方法引數:" + arg); } //目標方法的回傳值 System.out.println(res); //后置通知 System.out.println("后置通知!!!"); }
測驗類
@Test public void demo02(){ String config = "applicationContext.xml"; ApplicationContext app = new ClassPathXmlApplicationContext(config); SomeService proxy = (SomeService)app.getBean("myService2"); proxy.doOther("Dick",20); }點擊并拖拽以移動
2.3.6 環繞通知:@Around
在目標方法執行之前之后執行,被注解為環繞增強的方法要有回傳值
被注解為環繞增強的方法要有回傳值,Object 型別,并且方法可以包含一個ProceedingJoinPoint型別的引數
介面ProceedingJoinPoint其中有一個proceed() 方法,用于執行目標方法
若目標方法有回傳值,則該方法的回傳值就是目標方法的回傳值,最后,環繞增強方法將其回傳值回傳,該增強方法實際是攔截了目標方法的執行
- 切面類
1. 環繞通知方法的定義格式:
- public
- 必須有一個回傳值,推薦使用Object
- 方法名稱自定義
- 方法有引數,固定的引數 ProceedingJoinPoint
2. 特點
- 在目標方法的前和后都能增強功能
- 控制目標方法是否被呼叫執行
- 修改原來的目標方法的執行結果,影響最后的呼叫結果
- 它是功能最強的通知
3.環繞通知等同于jdk動態代理的InvocationHandler介面
4.引數:ProceedingJoinPoint 就等同于 Method,用于執行目標方法
5.回傳值: 就是目標方法的執行結果,可以被修改
6.環繞通知:經常做事務, 在目標方法之前開啟事務,執行目標方法, 在目標方法之后提交事務
@Around(value = "https://www.cnblogs.com/laoduyyds/p/execution(String *..do(..))") public Object myAround(ProceedingJoinPoint p) throws Throwable { Object result = null; //前置功能增強 System.out.println("前置功能增強!!"); //等同于method.invoke(); Object result = doFirst(); result = p.proceed(); //后置功能增強 System.out.println("后置功能增強"); return result; }
測驗類
@Test public void demo01(){ ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); SomeService proxy = (SomeService)app.getBean("Service3"); String result = proxy.doFirst("Dick", 99); //輸出回傳結果 System.out.println(result); }
2.3.7 例外通知:@AfterThrowing
在目標方法拋出例外后執行
該注解的 throwing 屬性用于指定所發生的例外類物件
被注解為例外通知的方法可以包含一個引數 Throwable,引數名稱為 throwing 指定的名稱,表示發生的例外物件
- 業務方法
@Override public void doSecond() { System.out.println("執行業務方法doSecond()" + (10/0)); }
- 切面類
1. 例外通知方法的定義格式:
- 訪問權限public
- 沒有回傳值
- 方法名稱自定義
- 方法有個一個Exception, 也可以使用JoinPoint
2. @AfterThrowing:例外通知
- 屬性:
value 切入點運算式
throwinng 自定義的變數,表示目標方法拋出的例外物件,變數名必須和方法的引數名一樣
- 特點:
在目標方法拋出例外時執行的
可以做例外的監控程式, 監控目標方法執行時是不是有例外,如果有例外,可以發送郵件,短信進行通知
@AfterThrowing(value = "https://www.cnblogs.com/laoduyyds/p/execution(* *..SomeServiceImpl.doSecond(..))", throwing = "ex") public void myAfterThrowing(Exception ex) { System.out.println("例外通知:方法發生例外時,執行:"+ex.getMessage()); //發送郵件,短信,通知開發人員 }
2.3.8 最終通知:@After
無論目標方法是否拋出例外,該增強均會被執行
- 業務方法
@Override public void doThird() { System.out.println("執行業務方法doThird()"); }
切面類
1. 最終通知的定義格式:
- 訪問權限public
- 沒有回傳值
- 方法名稱自定義
- 方法沒有引數,但是可以使用JoinPoint
2. @After :最終通知特點
- 總是會執行
- 在目標方法之后執行
//等同以下執行方式 try{ SomeServiceImpl.doThird(..) }catch(Exception e){ }finally{ myAfter() } 點擊并拖拽以移動 @After(value = "execution(* *..SomeServiceImpl.doThird(..))") public void myAfter(){ System.out.println("執行最終通知,總是會被執行的代碼"); //一般做資源清除作業的, }
2.3.9 @Pointcut 定義切入點
當較多的通知增強方法使用相同的 execution 切入點運算式時,撰寫、維護均較為麻煩;AspectJ 提供了@Pointcut 注解,用于定義 execution 切入點運算式
將@Pointcut 注解在一個方法之上,以后所有的 execution 的 value 屬性值均可使用該方法名作為切入點
代表的就是@Pointcut定義的切入點,這個使用@Pointcut注解的方法一般使用 private 的標識方法,即沒有實際作用的方法
- 切面類
1.@Pointcut: 定義和管理切入點, 如果你的專案中有多個切入點運算式是重復的,可以復用的,
2.特點:
當使用@Pointcut定義在一個方法的上面 ,此時這個方法的名稱就是切入點運算式的別名
其它的通知中,value屬性就可以使用這個方法名稱,代替切入點運算式了
@After(value = "https://www.cnblogs.com/laoduyyds/p/mypt()") public void myAfter(){ System.out.println("執行最終通知,總是會被執行的代碼"); //一般做資源清除作業的, } @Before(value = "mypt()") public void myBefore(){ System.out.println("前置通知,在目標方法之前先執行的"); } @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" ) private void mypt(){ //無需代碼, }
2.4 代理方式更換
如果目標類有介面,默認使用jdk動態代理,如果目標類沒有介面,則使用CGlib動態代理
如果想讓具有介面的目標類使用CGlib的代理方式,需要以下組態檔
<aop:aspectj-autoproxy proxy-target-class="true"/>
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/418058.html
標籤:Java
