淺談SpringAOP
0. 寫在最前面
之前實習天天在寫業務,其中有一個業務是非常的複雜,涉及到了特別多的表。最後測下來,一個介面的時間,竟然要5s多。
當時想寫一個AOP,來計算處理介面花費多長時間,也就是在業務邏輯的前面計算開始的時間,業務邏輯後面計算結束的時間,一相減即可。
但我發覺我竟然忘記怎麼寫了,哎,沒辦法,在此紀錄,重新撿回吧。
1. 什麼AOP
AOP(Aspect-Oriented Programming, 面向切面程式設計): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向物件程式設計) 的補充.
AOP 的主要程式設計物件是切面(aspect), 而切面模組化橫切關注點.
在應用 AOP 程式設計時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裡, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關注點就被模組化到特殊的物件(切面)裡.
AOP 的好處: 每個事物邏輯位於一個位置, 程式碼不分散, 便於維護和升級 業務模組更簡潔, 只包含核心業務程式碼.
簡單來說,AOP就是在不影響現有的業務邏輯下面,在業務前面後面織入一些其他邏輯。比如,日誌,驗證等等
2. SpringAOP有哪幾種
-
@Before:前置通知, 在方法執行之前執行
-
@After:後置通知, 在方法執行之後執行
-
@AfterRunning:返回通知, 在方法返回結果之後執行
-
@AfterThrowing:異常通知, 在方法丟擲異常之後
-
@Around:環繞通知, 圍繞著方法執行
假設我們現在有一個介面如下:
1 @RequestMapping("/aa") 2 public String a() { 3Person person = new Person("zhangsan", 18); 4redisService.put("person", person); 5Person ps = redisService.get("person"); 6return ps.toString(); 7 }
現在產品提了一個需求,我想在這段業務執行前,列印一句話,結束後在列印一句話。
很簡單,我們直接改業務程式碼就行了,一個可以兩個可以,但是多了呢,很嚴重影響業務邏輯,此時就可以用到了前置通知和後置通知。
1 @Pointcut(value = "execution (public * com.test.mybatis.demo.web.DepartmentController.*(..))") 2 public void pointCut() { } 3 4 @Before(value = "pointCut()") 5 public void beforeMethod(JoinPoint joinPoint) { 6String methodName = joinPoint.getSignature().getName(); 7List<Object> args = Lists.newArrayList(joinPoint.getArgs()); 8log.info("The method [" + methodName + "] begins with " + args); 9 } 10 11 @After(value = "pointCut()") 12 public void afterMethod(JoinPoint joinPoint) { 13String methodName = joinPoint.getSignature().getName(); 14log.info("The method [" + methodName + "] ends"); 15 }
解釋下這段程式碼。
PointCut 簡稱切點,也就是一個表示式,我要在哪些介面上進行攔截,進行一個配置。他有他自己的一套規範,我們按照他的規範來就好了。
JoinPoint 簡稱連線點。程式執行的某個特定位置:如類某個方法呼叫前、呼叫後、方法丟擲異常後等。JoinPoint 我可以獲取到方法名,引數等等。
@Before,@After一個是前置通知,一個是後置通知
此時我再訪問這個介面的時候,他的執行流程如下:
beforeMethod ==> 業務邏輯 ==> afterMethod
是不是非常的神奇,AOP的實現方式可以看我這邊博文:動態代理兩種實現方式
3. 回到我們的最初
如何計算一個介面耗時多少呢?
起初我想到可以用前置 + 後置 來做,但最後相減,變數怎麼傳遞不好搞。想過用全域性變數,但併發會出現問題。
此時我想到了很少用的環繞通知 ,最後程式碼如下:
1 @Around("pointCut()") 2 public Object aroundMethod(ProceedingJoinPoint joinPoint) { 3Object res = null; 4String methodName = joinPoint.getSignature().getName(); 5try { 6// 前置通知 7Stopwatch stopwatch = Stopwatch.createStarted(); 8// 執行目標方法 9res = joinPoint.proceed(); 10// 後置通知 11long duration = stopwatch.elapsed(TimeUnit.MILLISECONDS); 12log.info("{} 執行時長: {}", methodName, duration); 13} catch (Throwable throwable) { 14throwable.printStackTrace(); 15} 16return res; 17 }
每次執行一個方法,他都會將你的執行時間打印出來。結果如下:
1 // 前置通知 2 2019-01-24 21:17:52.802INFO 224 --- [nio-8080-exec-5] com.test.mybatis.demo.AOP.TestAOP: The method [testAOP] begins 3 // 業務邏輯 4 這裡是業務邏輯 5 // 環繞通知 6 2019-01-24 21:17:53.303INFO 224 --- [nio-8080-exec-5] com.test.mybatis.demo.AOP.TestAOP: testAOP 執行時長: 500 7 // 後置通知 8 2019-01-24 21:17:53.303INFO 224 --- [nio-8080-exec-5] com.test.mybatis.demo.AOP.TestAOP: The method [testAOP] ends
4. 寫在最後
最後我想將這個計算介面時間的AOP定義成一個註解,只要我在介面上加這個註解,他就會列印時間,否則就不列印。
這是後期的一個目標。共勉