輕鬆搞定AOP
整合Spring AOP步驟
1 引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.實體
@Slf4j @Aspect @Order(3) // 有多個日誌時,ORDER可以定義切面的執行順序(數字越大,前置越後執行,後置越前執行) @Component public class LogAspect { ThreadLocal<Long> startTime = new ThreadLocal<>(); @Pointcut("execution(public * cn.myframe.controller..*.*(..))") public void log() { } @Before("log()") public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); // 接收到請求,記錄請求內容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內容 log.info("URL : " + request.getRequestURL().toString()); log.info("HTTP_METHOD : " + request.getMethod()); log.info("IP : " + request.getRemoteAddr()); log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); log.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret", pointcut = "log()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內容 log.info("RESPONSE : " + ret); log.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); } }
通知位置的切入內容
-
使用@Before在切入點開始處切入內容
-
使用@After在切入點結尾處切入內容
-
使用@AfterReturning在切入點return內容之後切入內容(可以用來對處理返回值做一些加工處理)
-
使用@Around在切入點前後切入內容,並自己控制何時執行切入點自身的內容
-
使用@AfterThrowing用來處理當切入內容部分丟擲異常之後的處理邏輯
AOP切面的優先順序
由於通過AOP實現,程式得到了很好的解耦,但是也會帶來一些問題,比如:我們可能會對Web層做多個切面,校驗使用者,校驗頭資訊等等,這個時候經常會碰到切面的處理順序問題。
@Order(i)註解來標識切面的優先順序。i的值越小,優先順序越高。假設我們還有一個切面是CheckNameAspect用來校驗name必須為didi,我們為其設定@Order(10),而上文中WebLogAspect設定為@Order(5),所以WebLogAspect有更高的優先順序,這個時候執行順序是這樣的:
在@Before中優先執行@Order(5)的內容,再執行@Order(10)的內容 在@After和@AfterReturning中優先執行@Order(10)的內容,再執行@Order(5)的內容 所以我們可以這樣子總結: 在切入點前的操作,按order的值由小到大執行 在切入點後的操作,按order的值由大到小執行
定義切入點
切入點表示式的格式:execution([可見性] 返回型別 [宣告型別].方法名(引數) [異常])
其中[]的為可選,其他的還支援萬用字元的使用:
-
*:匹配所有字元
-
..:一般用於匹配多個包,多個引數
-
+:表示類及其子類
運算子有:&&、||、!
@Pointcut("execution(public * cn.myframe.controller..*.*(..))") 上面這段註解的意思如下: 1) execution(): 表示式主體 2) 第一個public *號:表示返回型別, *號表示所有的型別。 3) 包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.king.controller包、子孫包下所有類的方法。 4) 第二個*號:表示類名,*號表示所有的類。 5) *(..):最後這個星號表示方法名,*號表示所有的方法,後面括弧裡面表示方法的引數,兩個句點表示任何引數
常用切入點表示式關鍵詞
1)execution:用於匹配子表示式。
//匹配com.cjm.model包及其子包中所有類中的所有方法,返回型別任意,方法引數任意 @Pointcut("execution(* com.cjm.model...(..))") public void before(){}
2)within:用於匹配連線點所在的Java類或者包。
//匹配Person類中的所有方法 @Pointcut("within(com.cjm.model.Person)") public void before(){} //匹配com.cjm包及其子包中所有類中的所有方法 @Pointcut("within(com.cjm..*)") public void before(){}
3)@annotation :匹配連線點被它引數指定的Annotation註解的方法。也就是說,所有被指定註解標註的方法都將匹配。
@Pointcut("@annotation(com.cjm.annotation.AdviceAnnotation)") public void before(){}
4)target:用於向通知方法中傳入目標物件的引用。
@Before("before() && target(target) public void beforeAdvide(JoinPoint point, Object proxy){ }
5)bean:通過受管Bean的名字來限定連線點所在的Bean。該關鍵詞是Spring2.5新增的。
@Pointcut("bean(person)") public void before(){}
6)args:用於將引數傳入到通知方法中。
args
來繫結。如果在一個args表示式中應該使用型別名字的地方 使用一個引數名字,那麼當通知執行的時候對應的引數值將會被傳遞進來。
@Before("before() && args(age,username)") public void beforeAdvide(JoinPoint point, int age, String username){ }