Spring AOP使用之多切面運(yùn)行順序
Spring AOP多切面運(yùn)行順序
多切面運(yùn)行順序
當(dāng)一個方法的執(zhí)行被多個切面共同切的時候,環(huán)繞通知只影響當(dāng)前切面的通知順序,例如創(chuàng)建兩個切面logUtil,validateUtil兩個切面共同監(jiān)視計(jì)算器類的加法運(yùn)算,add(int a,int b);測試中,看切面工具類的名稱首字母,默認(rèn)情況下a-z執(zhí)行順序,所以這個時候logUtil切面通知比validateUtil先執(zhí)行通知;
所以順序是:L的前置通知 -->v的前置通知–>執(zhí)行add方法,然后v的后置通知–>V的后置返回–>L的后置通知–>L的后置返回。
但是當(dāng)logUtil中加入了環(huán)繞通知,所以環(huán)繞通知要比logUtil的普通通知先執(zhí)行,環(huán)繞通知功能很強(qiáng)大,在通過反射執(zhí)行方法的前面我們可以更改這個方法的參數(shù),但是普通通知不能這么做。雖然在logUtil加了環(huán)繞通知,但是這個環(huán)繞通知只是比logUtil的普通通知先執(zhí)行無論是進(jìn)入切面前還是出切面時,他并不影響validateUtil這個切面的普通通知的執(zhí)行順序,所以加了環(huán)繞通知執(zhí)行順序是
環(huán)繞前置–> log前置–>va前置–>目標(biāo)方法–>va后置–>va返回–>環(huán)繞返回通知–>環(huán)繞后置–>log后置–>log返回。
圖:
這里的validate切面就是圖中的VaAspect;
對啦,可以更改默認(rèn)的切面順序,要在將要更改的切面類上加入@order(int value)注解,value默認(rèn)值很大,超級大,越大執(zhí)行的優(yōu)先級越低,所以如果把它調(diào)成1就是先執(zhí)行這個切面的通知。
AOP的應(yīng)用場景
- aop可以進(jìn)行日志記錄;
- aop可以做權(quán)限驗(yàn)證
- AOP可以做安全檢查
- AOP可以做事務(wù)控制
回憶基于注解的AOC配置
- 將目標(biāo)類和切面類都加入到IOC容器中。@Component
- 告訴Spring哪個是切面類@Aspect
- 在切面類中使用五個通知注解來配置切面中的這些方法都什么時候在那運(yùn)行
- 開啟注解的aop功能。
不使用注解實(shí)現(xiàn)AOP配置。
1.切面類
public class LogUtil { public void performance(){} public void logStart(JoinPoint joinPoint) { //獲取方法上的參數(shù)列表 Object[] args = joinPoint.getArgs(); //獲取方法簽名 Signature signature = joinPoint.getSignature(); String name = signature.getName();//獲取方法名 System.out.println("前置通知:"+name+" 方法開始執(zhí)行了....參數(shù)是:"+ Arrays.asList(args) +""); } public void logReturn(JoinPoint point,Object result) { String name = point.getSignature().getName(); Object[] args = point.getArgs(); System.out.println("返回通知: "+name+"方法正常執(zhí)行,返回結(jié)果是:"+result+""); } public void logException(JoinPoint point,Exception e) { String name = point.getSignature().getName(); System.out.println("異常通知:"+name+" 方法出現(xiàn)了異常,異常是 "+e+"..."); } public void logEnd(JoinPoint joinPoint) { String name = joinPoint.getSignature().getName(); System.out.println("后置通知:"+name+"方法結(jié)束了"); } //環(huán)繞通知 public Object myAround(ProceedingJoinPoint proceedingJoinPoint){ Object proceed = null; //獲取方法名 String name = proceedingJoinPoint.getSignature().getName(); //獲取執(zhí)行方法的參數(shù)列表 Object[] args = proceedingJoinPoint.getArgs(); try { System.out.println("環(huán)繞前置通知:"+name+"方法開始執(zhí)行了,參數(shù)是"+Arrays.asList(args)+""); //等于 method.invoke();通過反射執(zhí)行指定方法 proceed = proceedingJoinPoint.proceed(); System.out.println("環(huán)繞返回通知:"+name+"方法返回結(jié)果是"+proceed+";"); } catch (Throwable throwable) { System.out.println("環(huán)繞異常通知:異常是"+throwable+""); throwable.printStackTrace(); }finally { System.out.println("環(huán)繞后置通知:"+name+"方法結(jié)束了"); } return proceed; }
2.被切入的類(這里是一個計(jì)算器類)
package main.java.cn.zixue.domain;public class MyCalculator { public int add(int a,int b) { return a+b; } public int sub(int a,int b) { return a-b; } public int mul(int a,int b) { return a*b; } public int dev(int a,int b) { return a/b; } public double add(double a,float b,int c) { return a+b+c; } }
3.配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="main.java.cn"></context:component-scan> <bean id="myCalculator" class="main.java.cn.zixue.domain.MyCalculator"></bean> <bean id="logUtil" class="main.java.cn.zixue.utils.LogUtil"></bean> <!--AOP名稱空間--> <aop:config> <!-- 制定切面的方法--> <aop:pointcut id="performance" expression="execution(public * main.java.cn.zixue.domain.MyCalculator.*(..))"></aop:pointcut> <!--指定切面--> <aop:aspect ref="logUtil"> <aop:after method="logEnd" pointcut-ref="performance"></aop:after> <aop:before method="logStart" pointcut-ref="performance"></aop:before> <aop:after-returning method="logReturn" pointcut-ref="performance" returning="result"></aop:after-returning> <aop:after-throwing method="logException" pointcut-ref="performance" throwing="e"></aop:after-throwing> <aop:around method="myAround" pointcut-ref="performance"></aop:around> </aop:aspect> </aop:config> </beans>
4.測試結(jié)果
@Test public void Test02() { MyCalculator myCalculator = (MyCalculator) context.getBean("myCalculator"); myCalculator.add(1,10); System.out.println("========================"); }
前置通知:add 方法開始執(zhí)行了…參數(shù)是:[1, 10]
環(huán)繞前置通知:add方法開始執(zhí)行了,參數(shù)是[1, 10]
環(huán)繞返回通知:add方法返回結(jié)果是11;
環(huán)繞后置通知:add方法結(jié)束了
返回通知: add方法正常執(zhí)行,返回結(jié)果是:11
后置通知:add方法結(jié)束了
====================**
普通前置通知->環(huán)繞通知->環(huán)繞返回->環(huán)繞后置->普通返回->普通后置
注解和配置文件在什么時候使用?該如何選擇?
注解的優(yōu)點(diǎn):配置快速簡潔。
配置文件的優(yōu)點(diǎn):功能豐富,注解有的他都可以實(shí)現(xiàn),注解沒有的他也有。
當(dāng)遇到重要的切面時,用配置文件寫,例如權(quán)限驗(yàn)證及管理。對于常用的普通的切面就用注解。
Spring AOP切面執(zhí)行順序和常見問題
切面注解的執(zhí)行順序
public Object aop(Method method,Object object) { try { try { /*doAround start*/ doBefore(); method.invoke(object); /*doAround end*/ } finally { doAfter(); } doAfterReturning(); } catch (Exception e) { doAfterThrowing(); } }
切面間的執(zhí)行順序
切面之間使用older注解,區(qū)分調(diào)用順序,Order值越小,那么切面越先執(zhí)行(越后結(jié)束).
不指定Order,那么Order是默認(rèn)值->Integer.MAX_VALUE. 如果Order相同,則是按照切面字母的順序來執(zhí)行切面.比如@Transactional和@Cacheable->對應(yīng)的切面是TransactionInterceptor和CacheInterceptor,則先執(zhí)行@Cacheable的切面
@Transactional也是通過切面實(shí)現(xiàn),Order值是Integer.MAX_VALUE。(如果在service方法上同時添加帶older的日志注解,在日志切面after里面報錯,不會回滾事務(wù))
常見問題示例
1.方法A調(diào)用同類中的方法B,方法B上的切面不會生效
問題示例:
@Component public class StrategyService extends BaseStrategyService { public PricingResponse getFactor(Map<String, String> pricingParams) { // 做一些參數(shù)校驗(yàn),以及異常捕獲相關(guān)的事情 return this.loadFactor(tieredPricingParams); } @Override @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2) private PricingResponse loadFactor(Map<String, String> pricingParams) { //代碼執(zhí)行 } }
原因:Spring的AOP是通過代理對象調(diào)用,只有這種調(diào)用方式,才能夠在真正的對象的執(zhí)行前后,能夠讓代理對象也執(zhí)行相關(guān)代碼,才能起到切面的作用。而對于上面使用this的方式調(diào)用,這種只是自調(diào)用,并不會使用代理對象進(jìn)行調(diào)用,也就無法執(zhí)行切面類。
public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(new SimplePojo()); factory.addInterface(Pojo.class); factory.addAdvice(new RetryAdvice()); Pojo pojo = (Pojo) factory.getProxy(); // this is a method call on the proxy! pojo.foo(); }
解決方法:使用AopContext.currentProxy()獲取到代理對象,然后通過代理對象調(diào)用對應(yīng)的方法。還有個地方需要注意,以上方式還需要將Aspect的expose-proxy設(shè)置成true。
@Component public class StrategyService{ public PricingResponse getFactor(Map<String, String> pricingParams) { // 做一些參數(shù)校驗(yàn),以及異常捕獲相關(guān)的事情 // 這里不使用this.loadFactor而是使用AopContext.currentProxy()調(diào)用,目的是解決AOP代理不支持方法自調(diào)用的問題 if (AopContext.currentProxy() instanceof StrategyService) { return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams); } else { // 部分實(shí)現(xiàn)沒有被代理過,則直接進(jìn)行自調(diào)用即可 return loadFactor(tieredPricingParams); } } @Override @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2) private PricingResponse loadFactor(Map<String, String> oricingParams) { //代碼執(zhí)行 } } //還有個地方需要注意,以上方式還需要將Aspect的expose-proxy設(shè)置成true。如果是配置文件修改: <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/> //如果是SpringBoot,則修改應(yīng)用啟動入口類的注解: @EnableAspectJAutoProxy(exposeProxy = true) public class Application { }
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
@Autowired注解注入的xxxMapper報錯問題及解決
這篇文章主要介紹了@Autowired注解注入的xxxMapper報錯問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11java調(diào)用微信接口實(shí)現(xiàn)網(wǎng)頁分享小功能
這篇文章主要為大家詳細(xì)介紹了java調(diào)用微信接口實(shí)現(xiàn)網(wǎng)頁分享小功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04Java實(shí)現(xiàn)兩人五子棋游戲(二) 畫出棋盤
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)兩人五子棋游戲,畫出五子棋的棋盤,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-03-03如何解決創(chuàng)建maven工程時,產(chǎn)生“找不到插件的錯誤”問題
這篇文章主要介紹了如何解決創(chuàng)建maven工程時,產(chǎn)生“找不到插件的錯誤”問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12IDEA 如何控制編輯左側(cè)的功能圖標(biāo)ICON(操作步驟)
很多朋友被idea左側(cè)的圖標(biāo)不見了這一問題搞的焦頭爛額,不知道該怎么操作,今天小編就交大家如何控制編輯左側(cè)的功能圖標(biāo) ICON,文字內(nèi)容不多,主要通過兩張截圖給大家說明,感興趣的朋友一起看看吧2021-05-05Java多線程(單例模式,阻塞隊(duì)列,定時器,線程池)詳解
本文是多線程初級入門,主要介紹了多線程單例模式、阻塞隊(duì)列、定時器、線程池、多線程面試考點(diǎn),感興趣的小伙伴可以跟隨小編一起了解一下2022-09-09SpringBoot+OCR?實(shí)現(xiàn)圖片文字識別
本文主要介紹了SpringBoot+OCR 實(shí)現(xiàn)圖片文字識別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12基于JavaScript動態(tài)規(guī)劃編寫一個益智小游戲
最近在學(xué)習(xí)動態(tài)規(guī)劃相關(guān)的知識,所以本文將利用動態(tài)規(guī)劃編寫一個簡單的益智小游戲,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-06-06