歡迎關注個人主頁:逸狼
創造不易,可以點點贊嗎
如有錯誤,歡迎指出~
AOP是Spring框架的第??核?(第??核?是 IoC)
什么是AOP?
? AspectOrientedProgramming(?向切?編程) 什么是?向切?編程呢?
切?就是指某?類特定問題,所以AOP也可以理解為?向特定?法編程.
什么是?向特定?法編程呢??如"登錄校驗",就是?類特定問題.登錄校驗攔截器,就是對"登錄校驗"這類問題的統?處理.所以,攔截器也是AOP的?種應?.AOP是?種思想,攔截器是AOP 思想的?種實現.Spring框架實現了這種思想,提供了攔截器技術的相關接?.
同樣的,統?數據返回格式和統?異常處理,也是AOP思想的?種實現. 簡單來說: AOP是?種思想,是對某?類事情的集中處理.
什么是SpringAOP?
AOP是?種思想,它的實現?法有很多,有SpringAOP,也有AspectJ、CGLIB等. SpringAOP是其中的?種實現?式. 學會了統?功能之后,是不是就學會了SpringAOP呢,當然不是. 攔截器作?的維度是URL(?次請求和響應),@ControllerAdvice 應?場景主要是全局異常處理 (配合?定義異常效果更佳),數據綁定,數據預處理.AOP作?的維度更加細致(可以根據包、類、?法 名、參數等進?攔截),能夠實現更加復雜的業務邏輯.
舉個例?: 我們現在有?個項?,項?中開發了很多的業務功能
比如想要記錄每個方法的耗時?,記錄開始時間,結束時間,再計算耗時,如果是常規寫法,每個方法都要重復書寫這些代碼,AOP就是將這些重復代碼提取出來,
AOP可以在不改變原有的代碼的前提下, 增強原來方法的功能(?侵?性:解耦)?
//通過id查詢圖書@RequestMapping("/queryBookById")public BookInfo queryBookById(Integer bookId){long start = System.currentTimeMillis();log.info("獲取圖書信息, bookId: "+ bookId);//參數校驗,不能為null,不能<=0...省略BookInfo bookInfo = bookService.queryBookById(bookId);long end = System.currentTimeMillis();log.info("queryBookById 耗時: " + (end - start) + "ms");return bookInfo;}
?SpringAOP快速??
學習什么是AOP后,我們先通過下?的程序體驗下AOP的開發,并掌握Spring中AOP的開發步驟.
需求:統計圖書系統各個接??法的執?時間.
引?AOP依賴
在pom.xml?件中添加配置
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
統計執?時間
package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
@Slf4j
public class TimeRecordAspect {//作用域,執行路徑@Around("execution(* com.example.demo.controller.*.*(..))")public Object timeRecord(ProceedingJoinPoint pjt){//1.記錄開始時間//2.執行目標方法時間//3.記錄結束時間//4.返回結果long start = System.currentTimeMillis();//執行目標方法Object o = null;try {o = pjt.proceed();} catch (Throwable e) {e.printStackTrace();}long end = System.currentTimeMillis();log.info(pjt.getSignature() + "耗時: "+ (end - start)+ "ms");return o;}
}
- 1. @Aspect:標識這是?個切?類
- 2. @Around:環繞通知,在?標?法的前后都會被執?.后?的表達式表?對哪些?法進?增強.
- 3. ProceedingJoinPoint.proceed()讓原始?法執?
我們通過AOP??程序完成了業務接?執?耗時的統計. 通過上?的程序,我們也可以感受到AOP?向切?編程的?些優勢:
- 代碼?侵?:不修改原始的業務?法,就可以對原始的業務?法進?了功能的增強或者是功能的改變
- 減少了重復代碼
- 提?開發效率
- 維護?便
SpringAOP核?概念
切點(Pointcut)
切點(Pointcut),也稱之為"切?點" Pointcut的作?就是提供?組規則(使?AspectJpointcutexpressionlanguage來描述),告訴程序對 哪些?法來進?功能增強.
表達式execution(* com.example.demo.controller.*.*(..)) 就是切點表達式
連接點(JoinPoint)
滿?切點表達式規則的?法,就是連接點.也就是可以被AOP控制的具體?法 以??程序舉例,所有com.example.demo.controller 路徑下的?法,都是連接點.
切點和連接點的關系 :?
連接點是滿?切點表達式的元素.
切點可以看做是保存了眾多連接點的?個集合.
通知(Advice)
通知就是具體要做的?作,指哪些重復的邏輯,也就是共性功能(最終體現為?個?法) ?如上述程序中記錄業務?法的耗時時間,就是通知.
切?(Aspect)
切?(Aspect)=切點(Pointcut)+通知(Advice) 通過切?就能夠描述當前AOP程序需要針對于哪些?法,在什么時候執?什么樣的操作.切?既包含了通知邏輯的定義,也包括了連接點的定義.
切?所在的類,我們?般稱為切?類(被@Aspect注解標識的類
通知類型
Spring中AOP的通知類型有以下?種:
- @Around:環繞通知,此注解標注的通知?法在?標?法前,后都被執?
- @Before:前置通知,此注解標注的通知?法在?標?法前被執?
- @After:后置通知,此注解標注的通知?法在?標?法后被執?,?論是否有異常都會執?
- @AfterReturning:返回后通知,此注解標注的通知?法在?標?法后被執?,有異常不會執?
- @AfterThrowing:異常后通知,此注解標注的通知?法發?異常后執?
示例代碼
package com.example.demo.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Slf4j
@Component
@Aspect
public class AspectDemo {//前置通知@Before("execution(* com.example.demo.controller.*.*(..))")public void doBefore() {log.info("執? Before ?法");}//后置通知@After("execution(* com.example.demo.controller.*.*(..))")public void doAfter() {log.info("執? After ?法");}//返回后通知@AfterReturning("execution(* com.example.demo.controller.*.*(..))")public void doAfterReturning() {log.info("執? AfterReturning ?法");}//拋出異常后通知@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")public void doAfterThrowing() {log.info("執? doAfterThrowing ?法");}//添加環繞通知@Around("execution(* com.example.demo.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Around ?法開始執?");Object result = joinPoint.proceed();log.info("Around ?法結束執?");return result;}
}
?程序正常運?的情況下,@AfterThrowing 標識的通知?法不會執?
從上圖也可以看出來,@Around 標識的通知?法包含兩部分,?個"前置邏輯",?個"后置邏輯".其 中"前置邏輯"會先于 @Before 標識的通知?法執?,"后置邏輯"會晚于 @After 標識的通知?法執??
如果發生異常
?
程序發?異常的情況下:
@AfterReturning 標識的通知?法不會執?, @AfterThrowing 標識的通知?法執?了
@Around 環繞通知中原始?法調?時有異常,通知中的環繞后的代碼邏輯也不會在執?了(因為 原始?法調?出異常了)
@PointCut
上?代碼存在?個問題,就是存在?量重復的切點表達式execution(* com.example.demo.controller.*.*(..)) ,?Spring提供了 @PointCut 注解,把公共的切點 表達式提取出來,需要?到時引?該切?點表達式即可.
@Slf4j
@Aspect
@Component
public class AspectDemo {//定義切點(公共的切點表達式) @Pointcut("execution(* com.example.demo.controller.*.*(..))")private void pt(){}//前置通知 @Before("pt()")public void doBefore() {//...代碼省略 }//后置通知 @After("pt()")public void doAfter() {//...代碼省略 }
當切點定義使?private修飾時,僅能在當前切?類中使?,當其他切?類也要使?當前切點定義時,就需 要把private改為public.引??式為:全限定類名.?法名()?
public class TimeRecordAspect {// @Around("execution(* com.example.demo.controller.*.*(..))")@Around("com.example.demo.aspect.AspectDemo.pt()")public Object timeRecord(ProceedingJoinPoint pjt){
...}
切?優先級@Order
當我們在?個項?中,定義了多個切?類時,并且這些切?類的多個切?點都匹配到了同?個?標?法. 當?標?法運?的時候,這些切?類中的通知?法都會執?,那么這?個通知?法的執?順序是什么樣 的呢?
存在多個切?類時,默認按照切?類的類名字?排序: ? @Before 通知:字?排名靠前的先執? ? @After 通知:字?排名靠前的后執?
但這種?式不?便管理,我們的類名更多還是具備?定含義的. Spring給我們提供了?個新的注解,來控制這些切?通知的執?順序:@Order 使??式如下:
@Slf4j
@Component
@Aspect
@Order(3)
public class demo1 {
...
}...
@Order(2)
public class demo2 {
...}...
@Order(1)
public class demo3 {
...}
@Order 控制切?的優先級,先執?優先級較?的切?,再執?優先級較低的切?,最終執??標?法.數字越小,優先級越高
切點表達式
上?的代碼中,我們?直在使?切點表達式來描述切點.下?我們來介紹?下切點表達式的語法. 切點表達式常?有兩種表達?式
execution
@annotation
execution表達式
execution()是最常?的切點表達式,?來匹配?法,語法為:
execution(訪問修飾符> 返回類型> 包名.類名.?法(?法參數)> 異常>)
其中:訪問 修飾符 和 異常 可以省略
?
切點表達式?例
TestController下的 public修飾,返回類型為String?法名為t1,?參?法
execution(public String com.example.demo.controller.TestController.t1())
省略訪問修飾符
execution(String com.example.demo.controller.TestController.t1())
匹配所有返回類型
execution(* com.example.demo.controller.TestController.t1())
匹配TestController下的所有?參?法
execution(* com.example.demo.controller.TestController.*())
匹配TestController下的所有?法
execution(* com.example.demo.controller.TestController.*(..))
匹配controller包下所有的類的所有?法
execution(* com.example.demo.controller.*.*(..))
匹配所有包下?的TestController
execution(* com..TestController.*(..))
匹配com.example.demo包下,?孫包下的所有類的所有?法
execution(* com.example.demo..*(..))
@annotation
execution表達式更適?有規則的,如果我們要匹配多個?規則的?法呢,?如:TestController中的t1() 和UserController中的u1()這兩個?法. 這個時候我們使?execution這種切點表達式來描述就不是很?便了. 我們可以借助?定義注解的?式以及另?種切點表達式 @annotation 來描述這?類的切點
實現步驟:
1. 編寫?定義注解
2. 使? @annotation 表達式來描述切點
3. 在連接點的?法上添加?定義注解
?定義注解
@TimeRecord 創建?個注解類(和創建Class?件?樣的流程,選擇Annotation就可以了)
package com.example.demo.aspect;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)//運行時
@Target({ElementType.METHOD})//表示作用在方法上public @interface TimeRecord {
}
@Target 標識了 Annotation 所修飾的對象范圍,即該注解可以?在什么地?.
@Retention 指Annotation被保留的時間?短,標明注解的?命周期
切?類
使? @annotation 切點表達式定義切點,只對@TimeRecord?效
@Aspect
@Component
@Slf4j
public class TimeRecordAspect {@Around("@annotation(com.example.demo.aspect.TimeRecord)")public Object timeRecord(ProceedingJoinPoint pjt){//1.記錄開始時間//2.執行目標方法時間//3.記錄結束時間//4.返回結果long start = System.currentTimeMillis();log.info("timeRecord.Around ?法開始執?");//執行目標方法Object o = null;try {o = pjt.proceed();} catch (Throwable e) {e.printStackTrace();}long end = System.currentTimeMillis();log.info(pjt.getSignature() + "耗時: "+ (end - start)+ "ms");log.info("timeRecord.Around ?法結束執?");return o;}
}
在TestController中的t1()和UserController中的u1()這兩個?法上添加?定義注解@TimeRecord ,其他?法不添加
@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {@TimeRecord@RequestMapping("/t1")public String t1(){log.info("執行t1");return "t1";}@RequestMapping("/t2")public int t2(){log.info("執行t2"); return "t2";}
@RequestMapping("/user")
@RestController
@Slf4j
public class UserController {@TimeRecord@RequestMapping("/u1")public String u1(){log.info("執行u1");return "u1";}@RequestMapping("/u2")public String u2(){log.info("執行u2");return "u2";}
}
如果要讓所有帶有@RequestMapping注解的方法都實現記錄時間,只需要將上面的切點表達式換成以下
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")