Springboot2(17)輕鬆搞定AOP
阿新 • • 發佈:2018-12-29
文章目錄
整合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){
}