1 初探Aop
1.1 何為AOP?
AOP (Aspect-Oriented Programming) 是一種編程范式,它提供一種將程序中的橫切關(guān)注點(diǎn)模塊化的方式。橫切關(guān)注點(diǎn)可以是日志、事務(wù)、安全等,它們不屬于業(yè)務(wù)邏輯,但是又必須要與業(yè)務(wù)邏輯緊密耦合在一起。在 AOP 中,我們將這些橫切關(guān)注點(diǎn)稱為“切面”,它們獨(dú)立于業(yè)務(wù)邏輯模塊,但是可以在程序運(yùn)行的不同階段被織入到業(yè)務(wù)邏輯中。使用 AOP 可以提高代碼復(fù)用性、降低模塊之間的耦合度、簡化代碼的維護(hù)性等。
1.2 AOP的組成
AOP由切面、切點(diǎn)、連接點(diǎn)和通知組成。
1.2.1 切面(Aspect)
切面是包含了通知、切點(diǎn)和切面的類,相當(dāng)于AOP實(shí)現(xiàn)的某個(gè)功能的集合。通俗理解,在程序中就是一個(gè)處理某方面具體問題的一個(gè)類。里面包含了許多方法,這些方法就是切點(diǎn)和通知。
1.2.2 連接點(diǎn)(Join Point)
應(yīng)?執(zhí)?過程中能夠插?切?的?個(gè)點(diǎn),這個(gè)點(diǎn)可以是?法調(diào)?時(shí),拋出異常時(shí),甚?修改字段時(shí)。切?代碼可以利?這些點(diǎn)插?到應(yīng)?的正常流程之中,并添加新的?為。連接點(diǎn)可以理解為可能會(huì)觸發(fā)AOP規(guī)則的所有點(diǎn)。 狹義可以理解為需要進(jìn)行功能增強(qiáng)的方法。
1.2.3 切點(diǎn)(Pointcut)
切點(diǎn)是連接點(diǎn)的集合。它定義了在哪些連接點(diǎn)上應(yīng)用特定的通知。通過使用切點(diǎn)表達(dá)式,可以根據(jù)連接點(diǎn)的特征(例如方法簽名或類名)選擇特定的連接點(diǎn)。即,切點(diǎn)是用來進(jìn)行主動(dòng)攔截的規(guī)則(配置)。
具體來說:Pointcut 的作?就是提供?組規(guī)則(使? AspectJ pointcut expression language 來描述)來匹配 Join Point,給滿?規(guī)則的 Join Point 添加 Advice。
1.2.4 通知(Advice)
在AOP術(shù)語中,切面的工作被稱之為通知。 通知是切面在連接點(diǎn)上執(zhí)行的動(dòng)作。它定義了在何時(shí)(例如在方法調(diào)用之前或之后)以及如何(例如打印日志或進(jìn)行性能監(jiān)控)應(yīng)用切面的行為。即,程序中被攔截請(qǐng)求觸發(fā)的具體動(dòng)作。
1.3 AOP的使用場景
回顧下筆者之前的文章,基于Servlet實(shí)現(xiàn)的前后端分離的博客系統(tǒng)中,除了登錄等?個(gè)功能不需要做?戶登錄驗(yàn)證之外,其他?乎所有??調(diào)?的前端控制器( Controller)都需要先驗(yàn)證?戶登錄的狀態(tài)。然?,當(dāng)系統(tǒng)的功能越來越多,則要寫的登錄驗(yàn)證也越來越多,一旦某些功能需要改動(dòng),這種處理方式由于耦合性很高,牽一發(fā)就會(huì)動(dòng)全身。?這些?法?是相同的,對(duì)于這種功能統(tǒng)?,且使?的地?較多的功能,就可以考慮 AOP來統(tǒng)?處理了。
例如,原本的博客系統(tǒng)在作者刪除、發(fā)布、瀏覽博客前都需要進(jìn)行登錄狀態(tài)的驗(yàn)證,如果用戶未登錄,則請(qǐng)求重定向到登錄界面。使用AOP后,在用戶調(diào)用Server服務(wù)之前,統(tǒng)一進(jìn)行校驗(yàn),驗(yàn)證通過則正常服務(wù),否則,被“攔截”。
除了統(tǒng)一登錄判斷外,使用AOP還可以實(shí)現(xiàn):
- 統(tǒng)??志記錄
- 統(tǒng)??法執(zhí)?時(shí)間統(tǒng)計(jì)
- 統(tǒng)?的返回格式設(shè)置
- 統(tǒng)?的異常處理
- 事務(wù)的開啟和提交等
2 Spring AOP入門
以上,我們已經(jīng)對(duì)AOP有了基本的了解。接下來,我們的目標(biāo)是嘗試使用Spring AOP來實(shí)現(xiàn)AOP的功能,完成的目標(biāo)如下:
攔截所有StudentController里的方法,即每次調(diào)用StudentController中的任意方法的時(shí)候,執(zhí)行相應(yīng)的通知事件。
Spring AOP 的實(shí)現(xiàn)步驟如下:
- 添加 Spring AOP 框架?持。
- 定義切?和切點(diǎn)。
(1)創(chuàng)建切面類
(2)配置攔截規(guī)則 - 定義通知。
2.1 添加 Spring AOP 框架?持
首先,創(chuàng)建Spring Boot項(xiàng)目
在pom.xml中添加Spring AOP的依賴配置:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 定義切面和切點(diǎn)
使用 @Aspect
注解表明當(dāng)前類為一個(gè)切面,而在切點(diǎn)中,我們要定義攔截的規(guī)則,具體實(shí)現(xiàn)如下:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect // 表明此類為一個(gè)切面
@Component // 隨著框架的啟動(dòng)而啟動(dòng)
public class StudentAspect {
// 定義切點(diǎn), 這里使用 Aspect 表達(dá)式語法
@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
public void pointcut(){ }
}
在上述實(shí)現(xiàn)代碼中,pointcut 為一個(gè)空方法,只是起到一個(gè)“標(biāo)識(shí)”的作用,即,標(biāo)識(shí)下面的通知方法具體指的是哪個(gè)切點(diǎn),切點(diǎn)可以有多個(gè)。
切點(diǎn)表達(dá)式由切點(diǎn)函數(shù)組成,其中 execution()
是最常?的切點(diǎn)函數(shù),?來匹配?法,語法為:
execution(<修飾符><返回類型><包.類.?法(參數(shù))><異常>)
修飾符和異??梢允÷?/strong>
*常見的切點(diǎn)表達(dá)式的示例:
-
匹配特定類的所有方法:
execution(* com.example.MyClass.*(..))
:匹配 com.example.MyClass 類中的所有方法。 -
匹配特定包下的所有方法:
execution(* com.example.*.*(..))
:匹配 com.example 包及其子包下的所有方法。 -
匹配特定注解標(biāo)注的方法:
execution(@com.example.MyAnnotation * *(..))
:匹配被 com.example.MyAnnotation 注解標(biāo)注的所有方法。 -
匹配特定方法名的方法:
execution(* com.example.MyClass.myMethod(..))
:匹配 com.example.MyClass 類中名為 myMethod 的方法。 -
匹配特定方法參數(shù)類型的方法:
execution(* com.example.MyClass.myMethod(String, int))
:匹配 com.example.MyClass 類中具有一個(gè) String 參數(shù)和一個(gè) int 參數(shù)的 myMethod 方法。 -
匹配特定返回類型的方法:
execution(String com.example.MyClass.myMethod(..))
:匹配 com.example.MyClass 類中返回類型為 String 的 myMethod 方法。
StudentController.java
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/student")
public class StudentController {
@RequestMapping("/hi")
public String sayHi(String name) {
System.out.println("執(zhí)行了 sayHi 方法~");
return "Hi," + name;
}
@RequestMapping("/hello")
public String sayHello() {
System.out.println("執(zhí)行了 sayHello 方法~");
return "Hello, hxh";
}
}
2.3 定義相關(guān)通知
通知定義的是被攔截方法具體要執(zhí)行的業(yè)務(wù)。
Spring 切?類中,可以在?法上使?以下注解,會(huì)設(shè)置?法為通知?法,在滿?條件后會(huì)通知本?法進(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)用之前和調(diào)?之后執(zhí)??定義的?為。
具體實(shí)現(xiàn)如下:
前置通知與后置通知(異常通知和返回后通知僅僅是注解不同,方式一致,這里不再贅述~)
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect // 表明此類為一個(gè)切面
@Component // 隨著框架的啟動(dòng)而啟動(dòng)
public class StudentAspect {
// 定義切點(diǎn), 這里使用 Aspect 表達(dá)式語法
@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
public void pointcut(){ }
// 前置通知
@Before("pointcut()")
public void beforeAdvice() {
System.out.println("執(zhí)行了前置通知~");
}
// 后置通知
@After("pointcut()")
public void afterAdvice() {
System.out.println("執(zhí)行了后置通知~");
}
}
環(huán)繞通知的具體實(shí)現(xiàn)
環(huán)繞通知是有Object返回值的,需要把執(zhí)行流程的結(jié)果返回給框架,框架拿到對(duì)象繼續(xù)執(zhí)行。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect // 表明此類為一個(gè)切面
@Component // 隨著框架的啟動(dòng)而啟動(dòng)
public class StudentAspect {
// 定義切點(diǎn), 這里使用 Aspect 表達(dá)式語法
@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")
public void pointcut(){ }
// 環(huán)繞通知
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) {
System.out.println("進(jìn)入環(huán)繞通知~");
Object obj = null;
// 執(zhí)行目標(biāo)方法
try {
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("退出環(huán)繞通知~");
return obj;
}
}
3 Spring AOP實(shí)現(xiàn)原理
Spring AOP 是通過動(dòng)態(tài)代理的?式,在運(yùn)?期將 AOP 代碼織?到程序中的,它的實(shí)現(xiàn)?式有兩種:JDK Proxy
和 CGLIB
。因此,Spring 對(duì) AOP 的支持局限于方法級(jí)別的攔截。
- CGLIB是Java中的動(dòng)態(tài)代理框架,主要作?就是根據(jù)?標(biāo)類和?法,動(dòng)態(tài)?成代理類。
- Java中的動(dòng)態(tài)代理框架,?乎都是依賴字節(jié)碼框架(如 ASM,Javassist 等)實(shí)現(xiàn)的。
- 字節(jié)碼框架是直接操作 class 字節(jié)碼的框架。可以加載已有的class字節(jié)碼?件信息,修改部分信息,或動(dòng)態(tài)?成?個(gè) class。
3.1 何為動(dòng)態(tài)代理?
動(dòng)態(tài)代理(Dynamic Proxy)是一種設(shè)計(jì)模式,它允許 在運(yùn)行時(shí)創(chuàng)建代理對(duì)象,并將方法調(diào)用轉(zhuǎn)發(fā)給實(shí)際的對(duì)象。 動(dòng)態(tài)代理可以用于實(shí)現(xiàn)橫切關(guān)注點(diǎn)(如日志記錄、性能監(jiān)控、事務(wù)管理等)的功能,而無需修改原始對(duì)象的代碼。
在Java中,動(dòng)態(tài)代理通常使用java.lang.reflect.Proxy
類和java.lang.reflect.InvocationHandler
接口來實(shí)現(xiàn)。
以下是使用動(dòng)態(tài)代理的一般步驟:
-
創(chuàng)建一個(gè)實(shí)現(xiàn)InvocationHandler接口的類,該類將作為代理對(duì)象的調(diào)用處理程序。在InvocationHandler接口的invoke方法中,可以定義在方法調(diào)用前后執(zhí)行的邏輯。
-
使用Proxy類的newProxyInstance方法創(chuàng)建代理對(duì)象。該方法接受三個(gè)參數(shù):類加載器、代理接口數(shù)組和調(diào)用處理程序。它將返回一個(gè)實(shí)現(xiàn)指定接口的代理對(duì)象。
-
使用代理對(duì)象調(diào)用方法。當(dāng)調(diào)用代理對(duì)象的方法時(shí),實(shí)際上會(huì)調(diào)用調(diào)用處理程序的invoke方法,并將方法調(diào)用轉(zhuǎn)發(fā)給實(shí)際的對(duì)象。
3.2 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)
先通過實(shí)現(xiàn) InvocationHandler 接?創(chuàng)建?法調(diào)?處理器,再通過 Proxy 來創(chuàng)建代理類。
// 動(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();
}
}
3.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, MethodProxy methodProxy) 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();
}
}
3.4 兩種方式的區(qū)別
- JDK 實(shí)現(xiàn),要求被代理類必須實(shí)現(xiàn)接?, 之后是通過 InvocationHandler 及 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ì)象。
寫在最后
?本文被 JavaEE編程之路 收錄點(diǎn)擊訂閱專欄 , 持續(xù)更新中。
?以上便是本文的全部內(nèi)容啦!創(chuàng)作不易,如果你有任何問題,歡迎私信,感謝您的支持!文章來源:http://www.zghlxwxcb.cn/news/detail-584439.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-584439.html
到了這里,關(guān)于【Spring】Spring AOP入門及實(shí)現(xiàn)原理剖析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!