- 博主簡介:想進(jìn)大廠的打工人
- 博主主頁:@xyk:
- 所屬專欄:?JavaEE進(jìn)階?
目錄
文章目錄
一、初識AOP
1.1 什么是AOP?
1.2 AOP的組成
1.2.1 切面(Aspect)
1.2.2 切點(Pointcut)
1.2.3?連接點(Join Point)
1.2.4 通知(Advice)
1.3 AOP的使用場景
二、Srping AOP 實現(xiàn)
2.1 添加Spring AOP 依賴
2.2 定義切面和切點
2.3 定義通知
三、Spring AOP 實現(xiàn)原理
3.1 什么是動態(tài)代理?
3.2 JDK 動態(tài)代理實現(xiàn)
3.3?CGLIB 動態(tài)代理實現(xiàn)
3.4?JDK 和 CGLIB 實現(xiàn)的區(qū)別
一、初識AOP
1.1 什么是AOP?
AOP(Aspect Oriented Programming):面向切面編程,它是?種思想,它是對某?類事情的
集中處理。在我們想要對某一件事情進(jìn)行集中處理,就可以使用到AOP,它提供一種將程序中的橫切關(guān)注點模塊化的方式。在 AOP 中,我們將這些橫切關(guān)注點稱為“切面”,它們獨立于業(yè)務(wù)邏輯模塊,但是可以在程序運行的不同階段被織入到業(yè)務(wù)邏輯中。
簡單來說,AOP 就是對某一件事進(jìn)行集中處理的思想方式~
1.2 AOP的組成
1.2.1 切面(Aspect)
切?(Aspect)由切點(Pointcut)和通知(Advice)組成,它既包含了橫切邏輯的定義,也包
括了連接點的定義。相當(dāng)于處理某方面具體問題的一個類,包含多個方法,而這些方法就是切點和通知。
1.2.2 切點(Pointcut)
Pointcut 的作?就是提供?組規(guī)則來匹配連接點(Join Point),給滿足規(guī)則的連接點添加通知(Advice),可以理解為用來進(jìn)行主動攔截的規(guī)則(配置)
1.2.3?連接點(Join Point)
應(yīng)?執(zhí)?過程中能夠插?切?的?個點,連接點可以理解為可能會觸發(fā)AOP規(guī)則的所有點。(所有請求)
1.2.4 通知(Advice)
在AOP術(shù)語中,切面的工作被稱之為通知。通知是切面在連接點上執(zhí)行的動作。它定義了在何時(例如在方法調(diào)用之前或之后)以及如何(例如打印日志或進(jìn)行性能監(jiān)控)應(yīng)用切面的行為。即,程序中被攔截請求觸發(fā)的具體動作。
Spring 切?類中,可以在方法上使?以下注解,會設(shè)置?法為通知方法,在滿?條件后會通知本
?法進(jìn)?調(diào)?:
- 前置通知使? @Before:通知?法會在?標(biāo)?法調(diào)?之前執(zhí)行。
- 后置通知使? @After:通知?法會在?標(biāo)?法返回或者拋出異常后調(diào)?。
- 返回之后通知使? @AfterReturning:通知?法會在?標(biāo)?法返回后調(diào)?。
- 拋異常后通知使? @AfterThrowing:通知?法會在?標(biāo)?法拋出異常后調(diào)?。
- 環(huán)繞通知使? @Around:通知包裹了被通知的?法,在被通知的?法通知之前和調(diào)?之后執(zhí)行?定義的行為。
1.3 AOP的使用場景
在做任何一個系統(tǒng)都需要登錄功能,那么幾乎想要使用這個系統(tǒng)都需要我們進(jìn)行驗證用戶登錄狀態(tài),我們之前的處理?式是每個 Controller 都要寫?遍?戶登錄驗證,然?當(dāng)你的功能越來越多,那么你要寫的登錄驗證也越來越多,?這些?法?是相同的,這么多的?法就會代碼修改和維護(hù)的成本。對于這種功能統(tǒng)?,且使?的地?較多的功能,就可以考慮 AOP來統(tǒng)?處理了。
?
除了統(tǒng)一登錄判斷外,使用AOP還可以實現(xiàn):
- 用戶登錄驗證
- 統(tǒng)??志記錄
- 統(tǒng)??法執(zhí)?時間統(tǒng)計
- 統(tǒng)?的返回格式設(shè)置
- 統(tǒng)?的異常處理
- 事務(wù)的開啟和提交等
二、Srping AOP 實現(xiàn)
Spring AOP 的實現(xiàn)步驟如下:
- 添加 Spring AOP 框架?持
- 定義切?和切點:(1)創(chuàng)建切面類(2)配置攔截規(guī)則
- 定義通知
2.1 添加Spring AOP 依賴
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 定義切面和切點
使用?@Aspect
?注解表明當(dāng)前類為一個切面,而在切點中,我們要定義攔截的規(guī)則,具體實現(xiàn)如下:
@Component // 隨著框架的啟動而啟動
@Aspect // 告訴框架我是一個切面類
public class UserAspect {
// 定義切點(配置攔截規(guī)則)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
}
在上述實現(xiàn)代碼中,pointcut 為一個空方法,只是起到一個“標(biāo)識”的作用,標(biāo)識下面的通知方法具體指的是哪個切點,切點可以有多個。
切點表達(dá)式由切點函數(shù)組成,其中?execution()
?是最常?的切點函數(shù),?來匹配?法,語法為:
execution(<修飾符><返回類型><包.類.?法(參數(shù))><異常>)
修飾符和異??梢允÷?/span>
常見的切點表達(dá)式的示例:
- 匹配特定類的所有方法:
- execution(* com.example.MyClass.*(..)):匹配 com.example.MyClass 類中的所有方法。
- 匹配特定包下的所有方法:
- execution(* com.example.*.*(..)):匹配 com.example 包及其子包下的所有方法。
- 匹配特定方法名的方法:
- execution(* com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 類中名為 myMethod 的方法。
- 匹配特定方法參數(shù)類型的方法:
- execution(* com.example.MyClass.myMethod(String, int)):匹配 com.example.MyClass 類中具有一個 String 參數(shù)和一個 int 參數(shù)的 myMethod 方法。
- 匹配特定返回類型的方法:
- execution(String com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 類中返回類型為 String 的 myMethod 方法。
package com.example.demo.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hi")
public String sayHi(String name){
System.out.println("執(zhí)行了Hi");
return "Hi," + name;
}
@RequestMapping("/hello")
public String sayHello(){
System.out.println("執(zhí)行了Hello");
return "Hello,world";
}
}
2.3 定義通知
通知定義的是被攔截方法具體要執(zhí)行的業(yè)務(wù)。我們上面列出了可以使用哪些通知~這里舉出例子
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component // 隨著框架的啟動而啟動
@Aspect // 告訴框架我是一個切面類
public class UserAspect {
// 定義切點(配置攔截規(guī)則)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){
}
@Before("pointcut()")
public void beforeAdvice(){
System.out.println("執(zhí)行了前置通知~");
}
@After("pointcut()")
public void AfterAdvice(){
System.out.println("執(zhí)行了后置通知~");
}
/**
* 環(huán)繞通知
* @param joinPoint
* @return
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
System.out.println("進(jìn)入了環(huán)繞通知~");
Object obj = null;
try {
// 執(zhí)?攔截?法
obj = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("退出了環(huán)繞通知~");
return obj;
}
}
環(huán)繞通知是在前置通知之前和后置通知之后運行的~
??
三、Spring AOP 實現(xiàn)原理
Spring AOP 是通過動態(tài)代理的?式,在運?期將 AOP 代碼織?到程序中的,它的實現(xiàn)?式有兩種:JDK Proxy
?和?CGLIB
。因此,Spring 對 AOP 的支持局限于方法級別的攔截。
- 默認(rèn)情況下,實現(xiàn)了接?的類,使? AOP 會基于 JDK ?成代理類
- 沒有實現(xiàn)接?的類,會基于 CGLIB ?成代理類
?
3.1 什么是動態(tài)代理?
動態(tài)代理(Dynamic Proxy)是一種設(shè)計模式,它允許 在運行時創(chuàng)建代理對象,并將方法調(diào)用轉(zhuǎn)發(fā)給實際的對象。 動態(tài)代理可以用于實現(xiàn)橫切關(guān)注點(如日志記錄、性能監(jiān)控、事務(wù)管理等)的功能,而無需修改原始對象的代碼。
在Java中,動態(tài)代理通常使用 java.lang.reflect.Proxy 類和 java.lang.reflect.InvocationHandler 接口來實現(xiàn)。
調(diào)用者在調(diào)用方法時,會先轉(zhuǎn)發(fā)給代理類創(chuàng)建的代理對象,隨后再由代理對象轉(zhuǎn)發(fā)給目標(biāo)對象。
以下是使用動態(tài)代理的一般步驟:文章來源:http://www.zghlxwxcb.cn/news/detail-639029.html
- 創(chuàng)建一個實現(xiàn)InvocationHandler接口的類,該類將作為代理對象的調(diào)用處理程序。在InvocationHandler接口的invoke方法中,可以定義在方法調(diào)用前后執(zhí)行的邏輯。
- 使用Proxy類的newProxyInstance方法創(chuàng)建代理對象。該方法接受三個參數(shù):類加載器、代理接口數(shù)組和調(diào)用處理程序。它將返回一個實現(xiàn)指定接口的代理對象。
- 使用代理對象調(diào)用方法。當(dāng)調(diào)用代理對象的方法時,實際上會調(diào)用調(diào)用處理程序的invoke方法,并將方法調(diào)用轉(zhuǎn)發(fā)給實際的對象。
?
3.2 JDK 動態(tài)代理實現(xiàn)
先通過實現(xiàn) InvocationHandler 接?創(chuàng)建?法調(diào)?處理器,再通過 Proxy 來創(chuàng)建代理類。文章來源地址http://www.zghlxwxcb.cn/news/detail-639029.html
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//動態(tài)代理:使?JDK提供的api(InvocationHandler、Proxy實現(xiàn)),此種?式實現(xiàn),要求被代理類必須實現(xiàn)接?
public class PayServiceJDKInvocationHandler implements InvocationHandler {
//?標(biāo)對象即就是被代理對象
private Object target;
public PayServiceJDKInvocationHandler( Object target) {
this.target = target;
}
//proxy代理對象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1.安全檢查
System.out.println("安全檢查");
//2.記錄?志
System.out.println("記錄?志");
//3.時間統(tǒng)計開始
System.out.println("記錄開始時間");
//通過反射調(diào)?被代理類的?法
Object retVal = method.invoke(target, args);
//4.時間統(tǒng)計結(jié)束
System.out.println("記錄結(jié)束時間");
return retVal;
}
public static void main(String[] args) {
PayService target= new AliPayService();
//?法調(diào)?處理器
InvocationHandler handler =
new PayServiceJDKInvocationHandler(target);
//創(chuàng)建?個代理類:通過被代理類、被代理實現(xiàn)的接?、?法調(diào)?處理器來創(chuàng)建
PayService proxy = (PayService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{PayService.class},
handler
);
proxy.pay();
}
}
3.3?CGLIB 動態(tài)代理實現(xiàn)
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
//被代理對象
private Object target;
public PayServiceCGLIBInterceptor(Object target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] args, Method
Proxy methodProxy) throws Throwable {
//1.安全檢查
System.out.println("安全檢查");
//2.記錄?志
System.out.println("記錄?志");
//3.時間統(tǒng)計開始
System.out.println("記錄開始時間");
//通過cglib的代理?法調(diào)?
Object retVal = methodProxy.invoke(target, args);
//4.時間統(tǒng)計結(jié)束
System.out.println("記錄結(jié)束時間");
return retVal;
}
public static void main(String[] args) {
PayService target= new AliPayService();
PayService proxy= (PayService) Enhancer.create(target.getClass(),n
ew PayServiceCGLIBInterceptor(target));
proxy.pay();
}
}
3.4?JDK 和 CGLIB 實現(xiàn)的區(qū)別
- JDK 實現(xiàn),要求被代理類必須實現(xiàn)接口, 之后是通過 InvocationHandler 及 Proxy,在運?時動態(tài)的在內(nèi)存中?成了代理類對象,該代理對象是通過實現(xiàn)同樣的接?實現(xiàn)(類似靜態(tài)代理接?實現(xiàn)的?式),只是該代理類是在運?期時,動態(tài)的織?統(tǒng)?的業(yè)務(wù)邏輯字節(jié)碼來完成。
- CGLIB 實現(xiàn),被代理類可以不實現(xiàn)接口, 是通過繼承被代理類,在運?時動態(tài)的?成代理類對象。
到了這里,關(guān)于【Spring】Spring AOP 初識及實現(xiàn)原理解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!