1. Spring AOP 是什么
學(xué)習(xí) Spring AOP 之前,先要了解 AOP 是什么
AOP(Aspect Oriented Programming):面向切面編程,它和 OOP(面向?qū)ο缶幊蹋╊愃啤?/p>
它是一種思想,是對(duì)某一類事情的集中處理。
比如用戶登錄權(quán)限的效驗(yàn),在學(xué)習(xí) AOP 之前,在需要判斷用戶登錄的頁(yè)面,都要各自實(shí)現(xiàn)或調(diào)用用戶驗(yàn)證的方法,學(xué)習(xí) AOP 之后,我們只需要在某一處配置一下,那么所有需要判斷用戶登錄的頁(yè)面就全部可以實(shí)現(xiàn)用戶登錄驗(yàn)證了,不用在每個(gè)方法中都寫用戶登錄驗(yàn)證了
AOP 是一種思想,而 Spring AOP 是實(shí)現(xiàn)(框架),這種關(guān)系和 IOC(思想)與 DI(實(shí)現(xiàn))類似
2. 為什么要用 AOP
- 高頻:對(duì)于這種功能統(tǒng)一,且使用地方較多的功能,可以考慮用 AOP 來處理(比如 用戶登錄驗(yàn)證)
- 使? AOP 可以擴(kuò)充多個(gè)對(duì)象的某個(gè)能?,AOP 可以說是 OOP 的補(bǔ)充和完善(比如 現(xiàn)在要實(shí)現(xiàn)的業(yè)務(wù)和這個(gè)通用的功能沒什么關(guān)系,但處于安全考慮,又必須進(jìn)行登錄的驗(yàn)證)
除了統(tǒng)一的用戶登錄判斷外,AOP 還可以實(shí)現(xiàn)
- 統(tǒng)一日志處理
- 統(tǒng)一方法執(zhí)行時(shí)間統(tǒng)計(jì)
- 統(tǒng)一的返回格式設(shè)置
- 統(tǒng)一的異常處理
- 事務(wù)的開啟和提交等
3. 怎么學(xué) Spring AOP
Spring AOP 學(xué)習(xí)主要分為3個(gè)部分
- 學(xué)習(xí) AOP 是如何組成的
- 學(xué)習(xí) Spring AOP 使用
- 學(xué)習(xí) Spring AOP 實(shí)現(xiàn)原理
4. AOP 組成
(1)切面(Aspect)
定義 AOP 是針對(duì)某個(gè)統(tǒng)一的功能的,這個(gè)功能就叫做一個(gè)切面,比如用戶登錄功能或方法的統(tǒng)計(jì)日志,他們就各是一個(gè)切面。切面是由切點(diǎn)和通知組成的
(2)連接點(diǎn)(Join Point)
所有可能觸發(fā) AOP(攔截方法的點(diǎn))就稱為連接點(diǎn)
(3)切點(diǎn)(Pointcut)
切點(diǎn)的作用就是提供一組規(guī)則來匹配連接點(diǎn),給滿足規(guī)則的連接點(diǎn)添加通知,總的來說就是,定義 AOP 攔截的規(guī)則的
切點(diǎn)相當(dāng)于保存了眾多連接點(diǎn)的一個(gè)集合(如果把切點(diǎn)看成一個(gè)表,而連接點(diǎn)就是表中一條一條的數(shù)據(jù))
(4)通知(Advice)
切面的工作就是通知
通知:規(guī)定了 AOP 執(zhí)行的時(shí)機(jī)和執(zhí)行的方法
Spring 切面類中,可以在方法上使用以下注解,會(huì)設(shè)置方法為通知方法,在滿足條件后悔通知本方法進(jìn)行調(diào)用
- 前置通知 @Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行
- 后置通知 @After:通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用
- 返回之后通知 @AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用
- 拋異常后通知:@AfterThrowing:通知方法會(huì)在目標(biāo)方法爬出異常之后調(diào)用
- 環(huán)繞通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為
舉個(gè)例子,在一個(gè)生產(chǎn)型公司中
通知相當(dāng)于底層的執(zhí)行者,切點(diǎn)是小領(lǐng)導(dǎo)制定規(guī)則,切面是大領(lǐng)導(dǎo)制定公司的發(fā)展方向,連接點(diǎn)是屬于一個(gè)普通的消費(fèi)者用戶
以多個(gè)??都要訪問?戶登錄權(quán)限為例子,AOP 整個(gè)組成部分如圖所示
5. Spring AOP 實(shí)現(xiàn)
Spring AOP 實(shí)現(xiàn)步驟
- 添加 Spring AOP 框架支持
- 定義切面和切點(diǎn)
- 實(shí)現(xiàn)通知
接下來我們使? Spring AOP 來實(shí)現(xiàn)?下 AOP 的功能,完成的?標(biāo)是攔截所有 UserController ??的
?法,每次調(diào)? UserController 中任意?個(gè)?法時(shí),都執(zhí)?相應(yīng)的通知事件。
5.1 添加 Spring AOP 框架支持
在中央倉(cāng)庫(kù)中搜鎖 Spring AOP Maven Repository: Search/Browse/Explore (mvnrepository.com)
在 pom.xml 中添加如下配置:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.2 定義切面和切點(diǎn)
@Aspect // 當(dāng)前類是一個(gè)切面
@Component
public class UserAspect {
// 定義一個(gè)切點(diǎn)(設(shè)置攔截規(guī)則)
@Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
public void pointcut() {
}
}
5.3 實(shí)現(xiàn)通知方法
- 前置通知 @Before:通知方法會(huì)在目標(biāo)方法調(diào)用之前執(zhí)行
- 后置通知 @After:通知方法會(huì)在目標(biāo)方法返回或者拋出異常后調(diào)用
- 返回之后通知 @AfterReturning:通知方法會(huì)在目標(biāo)方法返回后調(diào)用
- 拋異常后通知:@AfterThrowing:通知方法會(huì)在目標(biāo)方法爬出異常之后調(diào)用
- 環(huán)繞通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為
實(shí)現(xiàn)通知方法也就是在什么時(shí)機(jī)執(zhí)行什么方法
@Aspect // 當(dāng)前類是一個(gè)切面
@Component
public class UserAspect {
// 定義一個(gè)切點(diǎn)(設(shè)置攔截規(guī)則)
@Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
public void pointcut() {
}
// 定義 pointcut 切點(diǎn)的前置通知
@Before("pointcut()")
public void doBefore() {
System.out.println("執(zhí)行前置通知");
}
// 后置通知
@After("pointcut()")
public void doAfter() {
System.out.println("執(zhí)行后置通知");
}
// 返回之后通知
@AfterReturning("pointcut()")
public void doAfterReturning() {
System.out.println("執(zhí)行返回之后通知");
}
// 拋出異常之后通知
@AfterThrowing("pointcut()")
public void doAfterThrowing() {
System.out.println("執(zhí)行拋出異常之后通知");
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/sayhi")
public String sayHi() {
System.out.println("sayhi 方法被執(zhí)行");
int num = 10/0;
return "你好,java";
}
@RequestMapping("/sayhi2")
public String sayHi2() {
System.out.println("sayhi2 方法被執(zhí)行");
return "你好,java2";
}
}
環(huán)繞通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和調(diào)用之后執(zhí)行自定義的行為
// 添加環(huán)繞通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
Object result = null;
System.out.println("環(huán)繞通知:前置方法");
try {
// 執(zhí)行攔截方法
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("環(huán)繞通知:后置方法");
return result;
}
5.4 使? AOP 統(tǒng)計(jì) UserController 每個(gè)?法的執(zhí)?時(shí)間 StopWatch
Spring AOP 中統(tǒng)計(jì)時(shí)間用 StopWatch 對(duì)象
// 添加環(huán)繞通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
// spring 中的時(shí)間統(tǒng)計(jì)對(duì)象
StopWatch stopWatch = new StopWatch();
Object result = null;
try {
stopWatch.start(); // 統(tǒng)計(jì)方法的執(zhí)行時(shí)間,開始計(jì)時(shí)
// 執(zhí)行目標(biāo)方法,以及目標(biāo)方法所對(duì)應(yīng)的相應(yīng)通知
result = joinPoint.proceed();
stopWatch.stop(); // 統(tǒng)計(jì)方法的執(zhí)行時(shí)間,停止計(jì)時(shí)
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println(joinPoint.getSignature().getDeclaringTypeName() + "." +
joinPoint.getSignature().getName() +
"執(zhí)行花費(fèi)的時(shí)間:" + stopWatch.getTotalTimeMillis() + "ms");
return result;
}
5.4 切點(diǎn)表達(dá)式說明 AspectJ
AspectJ 表達(dá)式語法:SpringAOP & AspectJ
@Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
AspectJ 語法(Spring AOP 切點(diǎn)的匹配語法):
切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中 execution() 是最常?的切點(diǎn)函數(shù),?來匹配?法,語法為:
execution(<修飾符><返回類型><包.類.?法(參數(shù))><異常>)
AspectJ ?持三種通配符
* :匹配任意字符,只匹配?個(gè)元素(包,類,或?法,?法參數(shù))
… :匹配任意字符,可以匹配多個(gè)元素 ,在表示類時(shí),必須和 * 聯(lián)合使?。
+ :表示按照類型匹配指定類的所有類,必須跟在類名后?,如 com.cad.Car+ ,表示繼承該類的所有?類包括本身
修飾符,一般省略
- public 公共方法
- *任意
返回值,不能省略
- void 返回沒有值
- String 返回值字符串
- *任意
包,通常不省略,但可以省略
- com.gyf.crm 固定包
- com.gyf.crm.*.service crm 包下面子包任意(例如:com.gyf.crm.staff.service)
- com.gyf.crm… crm 包下面的所有子包(含自己)
- com.gyf.crm.*service… crm 包下面任意子包,固定目錄 service,service 目錄任意包
類,通常不省略,但可以省略
UserServiceImpl 指定類
*Impl 以 Impl 結(jié)尾
User* 以 User 開頭
*任意
方法名,不能省略
addUser 固定方法
add* 以 add 開頭
*DO 以 DO 結(jié)尾
*任意
參數(shù)
() 無參
(int) 一個(gè)整形
(int,int)兩個(gè)整型
(…) 參數(shù)任意
throws可省略,一般不寫
表達(dá)式示例
- execution(* com.cad.demo.User.*(…)) :匹配 User 類?的所有?法
- execution(* com.cad.demo.User+.*(…)) :匹配該類的?類包括該類的所有?法
- execution(* com.cad..(…)) :匹配 com.cad 包下的所有類的所有?法
- execution(* com.cad….(…)) :匹配 com.cad 包下、?孫包下所有類的所有?法
- execution(* addUser(String, int)) :匹配 addUser ?法,且第?個(gè)參數(shù)類型是 String,第?個(gè)參數(shù)類型是 int
6. Spring AOP 實(shí)現(xiàn)原理
Spring AOP 是構(gòu)建在動(dòng)態(tài)代理基礎(chǔ)上,因此 Spring 對(duì) AOP 的支持局限于方法級(jí)別的攔截
Spring AOP 動(dòng)態(tài)代理實(shí)現(xiàn):
默認(rèn)情況下,實(shí)現(xiàn)了接?的類,使? AOP 會(huì)基于 JDK ?成代理類,沒有實(shí)現(xiàn)接?的類,會(huì)基于 CGLIB ?成代理類
-
JDK Proxy(JDK 動(dòng)態(tài)代理)
-
CGLIB Proxy:默認(rèn)情況下 Spring AOP 都會(huì)采用 CGLIB 來實(shí)現(xiàn)動(dòng)態(tài)代理,因?yàn)樾矢?/p>
CGLIB 實(shí)現(xiàn)原理:通過繼承代理對(duì)象來實(shí)現(xiàn)動(dòng)態(tài)代理的(子類擁有父類的所有功能)
CGLIB 缺點(diǎn):不能代理最終類(也就是被 final 修飾的類)
6.1 生成代理的時(shí)機(jī) :織入(Weaving)
織入是把切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建新的代理對(duì)象的過程,切面在指定的連接點(diǎn)被織入到目標(biāo)對(duì)象中
在目標(biāo)對(duì)象的生命周期中有多個(gè)點(diǎn)可以進(jìn)行織入文章來源:http://www.zghlxwxcb.cn/news/detail-434631.html
- 編譯期:切面在目標(biāo)類編譯時(shí)被織入,這種方法需要特殊的編譯器,AspectJ 的織入編譯器就是以這種方式織入切面的
- 類加載期:切面在目標(biāo)類加載到 JVM 時(shí)被織入,這種方式需要特殊的類加載器,它可以在目標(biāo)類被引入應(yīng)用之前增強(qiáng)該目標(biāo)類的字節(jié)碼,AspectJ5 的加載時(shí)織入 (load-time weaving. LTW)就支持以這種方式織入切面
- 運(yùn)行期:切面在應(yīng)用運(yùn)行的某一時(shí)刻被織入,一般情況下,在織入切面時(shí),AOP容器會(huì)為目標(biāo)對(duì)象動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象,Spring AOP 就是以這種方式織入切面的
6.2 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)
JDK 動(dòng)態(tài)代理就是依靠反射來實(shí)現(xiàn)的文章來源地址http://www.zghlxwxcb.cn/news/detail-434631.html
//動(dòng)態(tài)代理:使?JDK提供的api(InvocationHandler、Proxy實(shí)現(xiàn)),此種?式實(shí)現(xiàn),要求被
代理類必須實(shí)現(xiàn)接?
public class PayServiceJDKInvocationHandler implements InvocationHandler {
//?標(biāo)對(duì)象即就是被代理對(duì)象
private Object target;
public PayServiceJDKInvocationHandler( Object target) {
this.target = target;
}
//proxy代理對(duì)象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.安全檢查
System.out.println("安全檢查");
//2.記錄?志
System.out.println("記錄?志");
//3.時(shí)間統(tǒng)計(jì)開始
System.out.println("記錄開始時(shí)間");
//通過反射調(diào)?被代理類的?法
Object retVal = method.invoke(target, args);
//4.時(shí)間統(tǒng)計(jì)結(jié)束
System.out.println("記錄結(jié)束時(shí)間");
return retVal;
}
public static void main(String[] args) {
PayService target= new AliPayService();
//?法調(diào)?處理器
InvocationHandler handler = new PayServiceJDKInvocationHandler(target);
//創(chuàng)建?個(gè)代理類:通過被代理類、被代理實(shí)現(xiàn)的接?、?法調(diào)?處理器來創(chuàng)建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),new Class[]{PayService.class},handler);
proxy.pay();
}
}
6.3 CGLIB 動(dòng)態(tài)代理實(shí)現(xiàn)
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
//被代理對(duì)象
private Object target;
public PayServiceCGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxymethodProxy)throws Throwable {
//1.安全檢查
System.out.println("安全檢查");
//2.記錄?志
System.out.println("記錄?志");
//3.時(shí)間統(tǒng)計(jì)開始
System.out.println("記錄開始時(shí)間");
//通過cglib的代理?法調(diào)?
Object retVal = methodProxy.invoke(target, args);
//4.時(shí)間統(tǒng)計(jì)結(jié)束
System.out.println("記錄結(jié)束時(shí)間");
return retVal;
}
public static void main(String[] args) {
PayService target= new AliPayService();
PayService proxy= (PayService) Enhancer.create(target.getClass(),
new PayServiceCGLIBInterceptor(target));
proxy.pay();
}
}
6.4 JDK 和 CGLIB 實(shí)現(xiàn)的區(qū)別
- JDK 實(shí)現(xiàn),要求被代理類必須實(shí)現(xiàn)接口,之后是通過 InvocationHander 及 Proxy,在運(yùn)行時(shí)動(dòng)態(tài)的在內(nèi)存中生成了代理對(duì)象,該代理對(duì)象是通過實(shí)現(xiàn)同樣的接口實(shí)現(xiàn)(類似靜態(tài)代理接口實(shí)現(xiàn)的方式),只是該代理類是在運(yùn)行期時(shí),動(dòng)態(tài)的織入統(tǒng)一的業(yè)務(wù)邏輯字節(jié)碼來完成的
- CGLIB 實(shí)現(xiàn),被代理類可以不實(shí)現(xiàn)接口,是通過繼承被代理類,在運(yùn)行時(shí)動(dòng)態(tài)的生成代理類對(duì)象,這種方式實(shí)現(xiàn)方式效率高
到了這里,關(guān)于Spring AOP(AOP概念、組成、Spring AOP實(shí)現(xiàn)及實(shí)現(xiàn)原理)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!