原文來自搬磚工,如需轉(zhuǎn)載請注明出處
1、簡介
AOP主要包括了通知、切點和連接點等術(shù)語,介紹以下:
1.通知(advice)
通知定義了切面是甚么和什么時候調(diào)用,什么時候調(diào)用包括以下幾種
Before 在方法被調(diào)用之前調(diào)用通知 After 在方法完成以后調(diào)用通知,不管方法履行是不是成功 After-returning 在方法成功履行以后調(diào)用通知 After-throwing 在方法拋出異常后調(diào)用通知 Around 通知包裹了被通知的方法,在被通知的方法調(diào)用之前和調(diào)用以后履行自定義的行動
2.切點(PointCut)
通知定義了切面的甚么和什么時候,切點定義了何處,切點的定義會匹配通知所要織入的1個或多個連接點,
我們通常使用明確的類的方法名稱來指定這些切點,或是利用正則表達式定義匹配的類和方法名稱來指定這些切點。
3.連接點(JoinPoint)
連接點是在利用履行進程中能夠插入切面的1個點,這個點可以是調(diào)用方法時,拋出異常時,乃至是修改1個字段時,
切面代碼可以利用這些連接點插入到利用的正常流程中,并添加新的行動,如日志、安全、事務(wù)、緩存等。
joinPoint.getSignature().getDeclaringTypeName() 可以獲得到調(diào)用方法的類名(包括包名), joinPoint.getSignature().getName() 可以獲得方法名, Arrays.toString(joinPoint.getArgs()) 得到的是方法調(diào)用的參數(shù)列表, joinPoint.proceed() 可以得到方法的返回結(jié)果
2、注解開發(fā)
聲明1個切面,只需要在類名上添加@Aspect屬性便可,具體的連接點,我們用@Pointcut和@Before、@After等標(biāo)注。
在聲明前 我們需要依賴配置pom
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.11</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.11</version> </dependency>例子:
package com.ganji.demo.service.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Service; /** * Created by admin on 2015/9/2. */ @Aspect @Service public class XmlAopDemoUserLog { // 配置切點 及要傳的參數(shù) @Pointcut("execution(* com.ganji.demo.service.user.UserService.GetDemoUser(..)) && args(id)") public void pointCut(int id) { } // 配置連接點 方法開始履行時通知 @Before("pointCut(id)") public void beforeLog(int id) { System.out.println("開始履行前置通知 日志記錄:"+id); } // 方法履行完后通知 @After("pointCut(id)") public void afterLog(int id) { System.out.println("開始履行后置通知 日志記錄:"+id); } // 履行成功后通知 @AfterReturning("pointCut(id)") public void afterReturningLog(int id) { System.out.println("方法成功履行后通知 日志記錄:"+id); } // 拋出異常后通知 @AfterThrowing("pointCut(id)") public void afterThrowingLog(int id) { System.out.println("方法拋出異常后履行通知 日志記錄"+id); } // 環(huán)繞通知 @Around("pointCut(id)") public Object aroundLog(ProceedingJoinPoint joinpoint,int id) { Object result = null; try { System.out.println("環(huán)繞通知開始 日志記錄"+id); long start = System.currentTimeMillis(); //有返回參數(shù) 則需返回值 result = joinpoint.proceed(); long end = System.currentTimeMillis(); System.out.println("總共履行時長" + (end - start) + " 毫秒"); System.out.println("環(huán)繞通知結(jié)束 日志記錄"); } catch (Throwable t) { System.out.println("出現(xiàn)毛病"); } return result; } }
3、AOP切面中的同步問題
在WebLogAspect切面中,分別通過doBefore和doAfterReturning兩個獨立函數(shù)實現(xiàn)了切點頭部和切點返回后履行的內(nèi)容,
若我們想統(tǒng)計要求的處理時間,就需要在doBefore處記錄時間,并在doAfterReturning處通過當(dāng)前時間與開始處記錄的時間計算
得到要求處理的消耗時間。
那末我們是不是可以在WebLogAspect切面中定義1個成員變量來給doBefore和doAfterReturning1起訪問呢?是不是會有同步問題呢?
的確,直接在這里定義基本類型會有同步問題,所以我們可以引入ThreadLocal對象,像下面這樣進行記錄:
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(getClass()); ThreadLocal<Long> startTime = new ThreadLocal<>(); @Pointcut("execution(public * com.juzi.web..*.*(..))") public void webLog(){} @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); // 省略日志記錄內(nèi)容 } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 處理完要求,返回內(nèi)容 logger.info("RESPONSE : " + ret); logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); } }
4、AOP切面的優(yōu)先級
由于通過AOP實現(xiàn),程序得到了很好的解耦,但是也會帶來1些問題,比如:我們可能會對Web層做多個切面,校驗用戶,
校驗頭信息等等,這個時候常常會碰到切面的處理順序問題。
所以,我們需要定義每一個切面的優(yōu)先級,我們需要@Order(i)注解來標(biāo)識切面的優(yōu)先級。i的值越小,優(yōu)先級越高。
假定我們還有1個切面是CheckNameAspect用來校驗name必須為didi,我們?yōu)槠湓O(shè)置@Order(10),
而上文中WebLogAspect設(shè)置為@Order(5),所以WebLogAspect有更高的優(yōu)先級,這個時候履行順序是這樣的:
在@Before中優(yōu)先履行@Order(5)的內(nèi)容,再履行@Order(10)的內(nèi)容
在@After和@AfterReturning中優(yōu)先履行@Order(10)的內(nèi)容,再履行@Order(5)的內(nèi)容
所以我們可以這模樣總結(jié):
在切入點前的操作,按order的值由小到大履行
在切入點后的操作,按order的值由大到小履行