SpringBoot AOP
AOP(面向切面編程)是SpringBoot的兩大核心功能之一,功能非常強大,為解耦提供了非常優(yōu)秀的解決方案。
AOP術(shù)語
- 執(zhí)行點(Excutepoint):類初始化,方法調(diào)用
- 連接點(JoinPoint):執(zhí)行點 和 方位的組合,可以確定JoinPoint葱椭。比如類初始化前,初始化后口四,方法調(diào)用前孵运,方法調(diào)用后。
- 切點(PointCut):在眾多的執(zhí)行點中蔓彩,定位合適的執(zhí)行點治笨。ExcutePoint相當于數(shù)據(jù)庫中的記錄驳概,Pointcut相當于查詢條件。
- 增強(Advice):織入到目標類連接點上的一段代碼旷赖,除了代碼之后顺又,還有執(zhí)行點的方位信息。
- 目標對象(Target):增強邏輯的織入目標類等孵。
- 引介(Introduction):一種特殊的增強稚照,為類增加一些額外的屬性和方法,動態(tài)為業(yè)務(wù)類增加其他接口的實現(xiàn)邏輯俯萌,讓業(yè)務(wù)類成為這個接口的實現(xiàn)類果录。
- 代理(proxy):一個類被AOP織入后,產(chǎn)生了一個結(jié)果類咐熙,它便是融合了原類和增強邏輯的代理類弱恒。
- 切面(Aspect):切面由切點和增強組成既包括橫切邏輯定義,也包括連接點定義糖声。
AOP重點:
- 如何通過切點和增強定位到連接點
- 如何在增強中編寫切面的代碼
實現(xiàn)方式
-
添加MAVEN依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
正則匹配
-
創(chuàng)建切面類
/** * 日志切面 */ @Aspect @Component public class LogAspect { @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))") public void webLog(){} @Before("webLog()") public void deBefore(JoinPoint joinPoint) throws Throwable { // 接收到請求斤彼,記錄請求內(nèi)容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 記錄下請求內(nèi)容 System.out.println("URL : " + request.getRequestURL().toString()); System.out.println("HTTP_METHOD : " + request.getMethod()); System.out.println("IP : " + request.getRemoteAddr()); System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfeterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 處理完請求分瘦,返回內(nèi)容 System.out.println("方法的返回值 : " + ret); } //后置異常通知 @AfterThrowing("webLog()") public void throwss(JoinPoint jp){ System.out.println("方法異常時執(zhí)行....."); } //后置最終通知,final增強蘸泻,不管是拋出異常或者正常退出都會執(zhí)行 @After("webLog()") public void after(JoinPoint jp){ System.out.println("方法最后執(zhí)行....."); } //環(huán)繞通知,環(huán)繞增強嘲玫,相當于MethodInterceptor @Around("webLog()") public Object arround(ProceedingJoinPoint pjp) { System.out.println("方法環(huán)繞start....."); try { Object o = pjp.proceed(); System.out.println("方法環(huán)繞proceed悦施,結(jié)果是 :" + o); return o; } catch (Throwable e) { e.printStackTrace(); return null; } } }
結(jié)果:
方法環(huán)繞start..... URL : http://localhost:8092/first HTTP_METHOD : GET IP : 0:0:0:0:0:0:0:1 CLASS_METHOD : com.xncoding.aop.controller.UserController.first ARGS : [] 方法環(huán)繞proceed,結(jié)果是 :first controller 方法最后執(zhí)行..... 方法的返回值 : first controller
切面注解說明
- @Aspect 作用是把當前類標識為一個切面供容器讀取
- @Pointcut 定義切點去团,切點方法不用任何代碼抡诞,返回值是void,重要的是條件表達式
- @Before 標識一個前置增強方法土陪,相當于BeforeAdvice的功能
- @AfterReturning 后置增強昼汗,相當于AfterReturningAdvice,方法退出時執(zhí)行
- @AfterThrowing 異常拋出增強鬼雀,相當于ThrowsAdvice
- @After final增強顷窒,不管是拋出異常或者正常退出都會執(zhí)行
- @Around 環(huán)繞增強源哩,相當于MethodInterceptor
方法參數(shù)說明
除了@Around外鞋吉,每個方法里都可以加或者不加參數(shù)JoinPoint。
JoinPoint里包含了類名励烦、被切面的方法名谓着,參數(shù)等屬性,可供讀取使用坛掠。
@Around參數(shù)必須為ProceedingJoinPoint赊锚,pjp.proceed相應(yīng)于執(zhí)行被切面的方法治筒。
@AfterReturning方法里,可以加returning = “xxx”舷蒲,xxx即為在controller里方法的返回值矢炼,本例中的返回值是”first controller”。
@AfterThrowing方法里阿纤,可以加throwing = “XXX”句灌,讀取異常信息,如本例中可以改為:
//后置異常通知 @AfterThrowing(throwing = "ex", pointcut = "webLog()") public void throwss(JoinPoint jp, Exception ex){ System.out.println("方法異常時執(zhí)行....."); }
一般常用的有before和afterReturn組合欠拾,或者單獨使用Around胰锌,即可獲取方法開始前和結(jié)束后的切面。
關(guān)于切點PointCut
execution函數(shù)用于匹配方法執(zhí)行的連接點藐窄,語法為:
execution(方法修飾符(可選) 返回類型 方法名 參數(shù) 異常模式(可選))
參數(shù)部分允許使用通配符:
表達式可由多個切點函數(shù)通過邏輯運算組成资昧,與(&&)、 或(||)荆忍、 非(!)
使用注解實現(xiàn)AOP
自定義注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Rentention(RetentionPolicy.RUNTIME)
public UserAccess {
String desc() default "無信息";
}
創(chuàng)建切面類
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class UserAccessAspect {
@Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
public void access() {
}
@Before("access()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
System.out.println("second before");
}
@Around("@annotation(userAccess)")
public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
//獲取注解里的值
System.out.println("second around:" + userAccess.desc());
try {
return pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
}
主要看一下@Around注解這里格带,如果需要獲取在controller注解中賦給UserAccess的desc里的值,就需要這種寫法刹枉,這樣UserAccess參數(shù)就有值了叽唱。
spring aop就是一個同心圓,要執(zhí)行的方法為圓心微宝,最外層的order最小棺亭。從最外層按照AOP1、AOP2的順序依次執(zhí)行doAround方法蟋软,doBefore方法镶摘。然后執(zhí)行method方法,最后按照AOP2岳守、AOP1的順序依次執(zhí)行doAfter凄敢、doAfterReturn方法。也就是說對多個AOP來說湿痢,先before的涝缝,一定后after。
對于上面的例子就是蒙袍,先外層的就是對所有controller的切面俊卤,內(nèi)層就是自定義注解的。 那不同的切面害幅,順序怎么決定呢消恍,尤其是同格式的切面處理,譬如兩個execution的情況以现,那spring就是隨機決定哪個在外哪個在內(nèi)了狠怨。
所以大部分情況下约啊,我們需要指定順序,最簡單的方式就是在Aspect切面類上加上@Order(1)注解即可佣赖,order越小最先執(zhí)行恰矩,也就是位于最外層。像一些全局處理的就可以把order設(shè)小一點憎蛤,具體到某個細節(jié)的就設(shè)大一點