Spring框架|AOP面向切面編程的十個細節


使用AOP可以實現在不侵入到目標方法的情況下,動態的將某一模塊加入IoC容器中。AOP的應用場景一般在日誌、權限驗證、安全檢查、事務控制。

一、IoC容器中保存的是組件的代理對象

假設使用動態代理後,從容器中獲取bean的類型,可以發現,獲得的是一個代理類型,說明AOP的底層就是動態代理,Ioc容器中保存的是組件的代理對象。

@ContextConfiguration(locations = "classpath:applicationContext.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class AOPTest {
	@Autowired
	Calculator ioc;
	
	@Test
	public void test() {
		System.out.println(ioc.getClass());
	}
}

在這裏插入圖片描述
補充:如果從IoC容器中使用類型來獲取到目標對象,一定要使用它的接口類型來獲取。如果沒有接口,Spring的cglib會自動爲我們創建代理對象。

二、切入點表達式的寫法

固定格式:execution(訪問權限符 返回值類型 方法全類名(參數表))
通配符:
(1)*

  • 匹配一個或多個字符。如:"execution(public int com.gql.impl.myMathCalculator.*.Math*(int, int))"
  • 匹配任意一個參數。如:"execution(public int com.gql.impl.myMathCalculator.*(int, *))"
  • 如果*放在路徑裏,只能匹配一層路徑。如:"execution(public int com.gql.impl.myMathCalculator.*(int, int))"
  • 權限位置不能寫*默認就是public的。

(2)..

  • 匹配任意任意個任意類型參數。如:"execution(public int com.gql.impl.myMathCalculator.*(..)"
  • 匹配任意多層路徑。如:"execution(public int com.gql..myMath*(..)"

記住兩種切入點表達式:
最模糊的:execution(* *.*(..))。代表任意包下任意類的任意方法,
最精確的:execution(public int com.gql.impl.myMathCalculator.add(int,int))
另外:切入點表達式還支持"&&"、"||"、"!"

三、通知方法的執行順序

正常執行:

  • ① @Before(前置通知)
  • ② @After(後置通知)
  • ③ @AfterReturning(正常返回)

異常執行:

  • ①@Before(前置通知)
  • ② @After(後置通知)
  • ③ @AfterThrowing(方法異常)

四、JoinPoint獲取目標方法的信息

在通知方法運行的時候,拿到目標方法的詳細信息。只需要在通知方法的參數列表上寫一個參數。

JoinPoint joinPoint:封裝了當前目標方法的詳細信息。

  • 獲得方法名:joinPoint.getSignature().getName()
  • 獲得參數列表:Arrays.asList(joinPoint.getArgs())

五、throwing、returning指定哪個參數用來接收異常、返回值

使用returning獲得方法返回的結果:

	@AfterReturning(value = "execution(public int com.gql.impl.myMathCalculator.*(int, int))", returning = "result")
	public static void logReturn(JoinPoint joinPoint, Object result) {
		System.out.println("[" + joinPoint.getSignature().getName() + "]方法執行完成,計算結果是[" + result + "]");
	}

使用throwing獲得異常信息:

	@AfterThrowing(value = "execution(public int com.gql.impl.myMathCalculator.*(int, int))", throwing = "e")
	public static void logException(JoinPoint joinPoint, Exception e) {
		System.out.println("[" + joinPoint.getSignature().getName() + "方法出現異常,異常信息是:");
	}

這種寫法和Ajax接收服務器數據特別像,只不過要告訴Spring這個變量的作用是接收返回值還是拋出異常。

六、Spring對通知方法的約束

Spring對通知方法的要求不嚴格,唯一有要求的是方法的參數列表一定不能亂寫。 通知方法是Spring利用反射調用的,每次方法調用需要確定這個方法的參數表的值。即參數列表上的每一個參數,Spring都得知道它是什麼。

  • Spring認識的參數:JoinPoint joinPoint。
  • Spring不認識的參數:使用直接中的屬性告訴Spring。

補充:在進行接收異常和接收返回值的時候,最好在參數中將異常的範圍、返回值的類型寫大一點。

七、抽取可重用的切入點表達式

在切面(Aspect)中聲明一個無返回類型的空方法,併爲方法標註@Pointcut註解。

	@Pointcut("execution(public int com.gql.impl.myMathCalculator.*(int, int))")
	public void myPoint() {
	
	}

後來的方法需要寫表達式時只需要將value的值填寫爲方法名即可。

八、環繞通知實際上就是動態代理

@Around環繞通知是Spring中最強大的通知,環繞通知實際上就是動態代理。

 * try{
 * 		//前置通知
 * 		method.invoke(obj,args);
 * 		//返回通知
 * }catch(e){
 * 		//異常通知
 * }finally{
 * 		//後置通知
 * }

環繞通知,就是上面代碼中的四個通知合起來的效果。下面演示環繞通知:

	@Around("myPoint()")
	public Object myAround(ProceedingJoinPoint pjp) {
		Object proceed = null;
		String methodName = pjp.getSignature().getName();
		try {
			System.out.println("環繞通知前:" + methodName + "方法開始執行");
			proceed = pjp.proceed(pjp.getArgs());// 相當於method.invoke()
			System.out.println("環繞通知後:" + methodName + "方法返回,返回值是" + proceed);
		} catch (Throwable e) {
			System.out.println(methodName + "方法出現異常,異常信息:" + e);
			throw new RuntimeException(e);
		} finally {
			System.out.println("環繞後置通知:" + methodName + "方法結束");
		}
		return proceed;
	}

上面的環繞通知中:

  • pjp.proceed(pjp.getArgs())實際上就是method.invoke(),即通過反射調用了方法。

如果程序正常執行:
在這裏插入圖片描述
如果程序出現異常:
在這裏插入圖片描述

九、同時聲明環繞通知和普通通知時的執行順序

環繞通知優先於普通通知執行,下面說明兩者都聲明時的執行順序:

 * [普通前置]
 * {
 *   try{
 *   	 環繞:前置
 *  	 目標方法執行
 * 		 環繞:返回
 * 	 }catch(){
 * 		環繞:出現異常
 *   }finally{
 *   	環繞:後置
 *   }
 * [普通後置]
 * [普通返回/異常]

真正的執行順序是:

  • ①環繞:前置 ②普通前置。(這兩個的執行順序是隨機的,不必關注)
  • ③目標方法執行
  • ④環繞:返回/環繞:出現異常
  • ⑤環繞:後置
  • ⑥普通後置
  • ⑦普通返回/異常

注意:如果環繞通知拿到異常不處理,普通通知就不會拿到異常,則普通通知感受到的方法是正常的,這是由於環繞通知把異常catch掉了。因此,爲了讓外界能夠知道這個異常,環繞通知中的異常一定要拋出去。

十、多切面運行順序

下圖的結果是LogUtils切面VaApsect切面LogUtils切面中的環繞通知,同時作用與一個方法時的執行順序:

  • 默認情況下是以開頭字母順序決定哪個切面先執行。
  • 可以通過@Order(num)指定切面執行順序,數值越小優先級越高。
    在這裏插入圖片描述
  • 牢記一句話:環繞是在哪個切面內,就只作用於哪個切面內!
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章