1.概述
面向切面編程(Aspect Orient Programming,AOP):是一種編程範式,旨在將 橫切關注點(Cross-Cutting Concerns,如日誌、事務、安全等) 從業務邏輯中分離出來,通過模塊化的方式增強代碼的可維護性和複用性。核心思想是通過“切面”定義通用功能,並在運行時動態織入到目標代碼中
橫切關注點(Cross-Cutting Concerns):指的是在系統中"橫向"跨越多個模塊、多個層次的功能需求,它們無法很好地被封裝在單個類或模塊中
1.1 場景舉例:監控業務性能
1.1.1 硬編碼實現
@Slf4j
public class HardCoding {
public void demo() {
long startTime = System.currentTimeMillis();
//業務代碼
log.info("消耗時間:{}", System.currentTimeMillis() - startTime);
}
public static void main(String[] args) {
new HardCoding().demo();
}
}
使用這種硬編碼方式監控業務性能主要有以下缺點:
- 代碼侵入性強:業務代碼與監控代碼耦合,修改監控代碼會影響業務代碼
- 重複代碼多:每個方法都要重複編寫監控代碼,維護困難
- 不利於管理:監控邏輯分散,難以統一管理
- 容易遺漏:開發人員可能忘記添加監控代碼
1.1.2 AOP實現
引入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>3.4.8</version> <!-- 應與SpringBoot版本一致 -->
</dependency>
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {
@RequestMapping("/a")
public void methodA(){
log.info("執行methodA");
}
@RequestMapping("/b")
public void methodB(){
log.info("執行methodB");
}
@RequestMapping("/c")
public void methodC(){
log.info("執行methodC");
}
}
@Component
@Slf4j
@Aspect
public class AOP {
@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//記錄開始時間
long startTime = System.currentTimeMillis();
//執行業務
Object result = joinPoint.proceed();
//計算時間
log.info("消耗時間:{}", System.currentTimeMillis() - startTime);
return result;
}
}
執行結果:
相較於硬編碼的方式,使用Spring AOP監控業務性能有很多顯著好處:
- 代碼解耦:業務代碼與監控代碼完全分離,易於維護
- 統一管理:所有監控邏輯集中處理,保持一致性和規範性
- 無侵入性:對現有代碼零侵入,新增監控不影響業務邏輯
- 複用性強:一套監控方案可以應用到整個項目中的所有方法
1.2 核心術語
- 切入點(Pointcut):提供一個表達式,用於指定對哪些方法進行增強。例如,上圖箭頭切到的
ABC業務的集合為一個切入點,具體能切到哪些方法與表達式有關- 連接點(Join Point):滿足切點表達式的方法。上圖中處理業務A/B/C的執行前後均為連接點(切入點包含連接點)
- 通知(Advice):連接點的共性功能(重複的邏輯)。如上圖箭頭執行的邏輯
- 切面(Aspect):定義了在何處(切入點)和何時(通知)執行額外邏輯,即
切入點+通知
2.Spring AOP
2.1 @Aspect
作用:用於標識一個類為切面(Aspect)。切面包含切入點(Pointcut)和通知(Advice),用於模塊化橫切關注點(如日誌、事務、權限等)
2.2 切入點
execution:是Spring AOP中定義切點的一種表達式,用於指定在哪些方法或類上應用通知(Advice)。它將橫切關注點(如日誌、事務)與業務邏輯分離,通過表達式匹配目標方法或類
//語法結構
execution(<訪問限定修飾符> <返回類型> <包名.類名.方法(方法參數)> <異常>)
*:匹配任意字符(除包分隔符外)
..:匹配任意子包或多級目錄;匹配任意數量參數
@Pointcut:是Spring AOP中的一個註解,用於定義一個可重用的切點表達式
2.3 通知類型
- ①Around註解·:最強大的通知類型,可以在目標方法執行前後完全控制其行為。它需要接收一個ProceedingJoinPoint類型參數,通過調用proceedingJoinPoint.proceed()來執行目標方法。Around通知可以修改返回值、處理異常或完全阻止目標方法執行
- ②Before註解:在目標方法執行前觸發,無法阻止方法執行(除非拋出異常)
- ③After註解:在目標方法完成後執行,無論方法是正常返回還是拋出異常
- ④AfterReturning註解:在目標方法正常返回後執行,可以通過訪問(不能修改)返回值
- returning屬性:指定接收返回值的參數名,參數類型必須與目標方法返回類型兼容
- ⑤AfterThrowing註解:只在目標方法拋出異常時執行
- throwing屬性:用於綁定目標方法拋出的異常對象
@Slf4j
@RestController
@RequestMapping("/demo")
public class Controller {
@RequestMapping("/a")
public Object methodA(Integer id){
log.info("執行methodA");
return id;
}
@RequestMapping("/b")
public void methodB(){
log.info("執行methodB");
throw new RuntimeException("發生異常");
}
}
@Component
@Slf4j
@Aspect
public class AOP {
@Pointcut("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public void pointcut(){}
@Around("pointcut()")
//@Around("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("doAround:業務執行前");
//執行業務
Object result = joinPoint.proceed();
log.info("doAround:業務執行後,result:{}", result);
return "success";
}
@Before("pointcut()")
//@Before("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public void doBefore(){
log.info("doBefore");
}
@After("pointcut()")
//@After("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public void doAfter(){
log.info("doAfter");
}
@AfterReturning(value = "pointcut()",returning = "id")
//@AfterReturning("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public void doAfterReturning(Integer id){
//發生異常時不執行
log.info("doAfterReturning,id:{}", id);
}
@AfterThrowing(value = "pointcut()",throwing = "throwable")
//@AfterThrowing("execution(* org.example.springaop.blog_demo.Controller.*(..))")
public void doAfterThrowing(Throwable throwable){
log.info("doAfterThrowing,throwable:{}", throwable.getMessage());
}
}
- 1.無異常拋出時,URL:127.0.0.1:8080/demo/a?id=1
- 2.有異常拋出時,URL:127.0.0.1:8080/demo/b
2.4 @annotation
作用:@annotation表達式用於匹配帶有指定註解的方法,是AOP中實現精準切入的關鍵方式之一。通過該表達式,可以攔截被特定註解標記的方法,實現邏輯增強
1.創建自定義註解
@Target(ElementType.METHOD)//註解級別:方法註解
@Retention(RetentionPolicy.RUNTIME)//生命週期:運行時
public @interface CustomizeAspect {
}
2.使用@annotation表達式描述切點
//添加到切面類中
@Around("@annotation(org.example.springaop.config.CustomizeAspect)")
public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("customize before");
Object result = joinPoint.proceed();
log.info("customize after,result:{}", result);
return "success";
}
3.將自定義註解添加到連接點上
//添加到Controller類中
@RequestMapping("/c")
@CustomizeAspect
public Object methodC(Integer id){
log.info("執行methodC");
return id;
}
執行結果:
2.5 @Order
作用:用於指定Bean的加載順序,其核心作用是通過數值定義優先級,數值越小優先級越高
@Component
@Slf4j
@Aspect
@Order(1)
public class AOP1 {
@Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
public void pointcut(){}
@Around("pointcut()")
//@Around("execution(* org.example.springaop.controller.Controller.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("doAround1:業務執行前");
//執行業務
Object result = joinPoint.proceed();
//計算時間
log.info("doAround1:業務執行後,result:{}", result);
return "success1";
}
@Before("pointcut()")
public void doBefore(){
log.info("doBefore1");
}
@After("pointcut()")
public void doAfter(){
log.info("doAfter1");
}
@AfterReturning(value = "pointcut()",returning = "id")
public void doAfterReturning(Integer id){
//發生異常時不執行
log.info("doAfterReturning1,id:{}", id);
}
@AfterThrowing(value = "pointcut()",throwing = "throwable")
public void doAfterThrowing(Throwable throwable){
log.info("doAfterThrowing1,throwable:{}", throwable.getMessage());
}
@Around("@annotation(org.example.springaop.config.CustomizeAspect)")
public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("customize before1");
Object result = joinPoint.proceed();
log.info("customize after1,result:{}", result);
return "success1";
}
}
@Component
@Slf4j
@Aspect
@Order(2)
public class AOP2 {
@Pointcut("execution(* org.example.springaop.controller.Controller.*(..))")
public void pointcut(){}
@Around("pointcut()")
//@Around("execution(* org.example.springaop.controller.Controller.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("doAround2:業務執行前");
//執行業務
Object result = joinPoint.proceed();
//計算時間
log.info("doAround2:業務執行後,result:{}", result);
return "success2";
}
@Before("pointcut()")
public void doBefore(){
log.info("doBefore2");
}
@After("pointcut()")
public void doAfter(){
log.info("doAfter2");
}
@AfterReturning(value = "pointcut()",returning = "id")
public void doAfterReturning(Integer id){
//發生異常時不執行
log.info("doAfterReturning2,id:{}", id);
}
@AfterThrowing(value = "pointcut()",throwing = "throwable")
public void doAfterThrowing(Throwable throwable){
log.info("doAfterThrowing2,throwable:{}", throwable.getMessage());
}
@Around("@annotation(org.example.springaop.config.CustomizeAspect)")
public Object customize(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("customize before2");
Object result = joinPoint.proceed();
log.info("customize after2,result:{}", result);
return "success2";
}
}
執行結果:
3.AOP底層原理
3.1 代理模式
代理模式(Proxy Pattern)通過創建一個代理對象來控制對目標對象的訪問。代理對象作為目標對象的替代品,可以在訪問前後添加額外邏輯(如權限控制、性能監控等)
3.2 靜態代理
代理類在編譯期確定,需要手動為每個目標類編寫代理類
以租房為例:租客(調用方)、房東(目標對象)、中介(代理對象)
public interface IHouse {
void rent();
void sell();
}
//房東
public class RealHouse implements IHouse {
@Override
public void rent() {
System.out.println("房東出租房子");
}
@Override
public void sell() {
System.out.println("房東售賣房子");
}
}
//中介
public class HouseProxy implements IHouse {
private final IHouse realHouse;
public HouseProxy(IHouse realHouse) {
this.realHouse = realHouse;
}
@Override
public void rent() {
System.out.println("開始代理");
realHouse.rent();
System.out.println("結束代理");
}
@Override
public void sell() {
System.out.println("開始代理");
realHouse.sell();
System.out.println("結束代理");
}
public static void main(String[] args) {
IHouse iHouse = new HouseProxy(new RealHouse());
iHouse.rent();
iHouse.sell();
}
}
執行結果:
開始代理
房東出租房子
結束代理
開始代理
房東售賣房子
結束代理
3.3 動態代理
AOP的底層原理依賴於
動態代理:不需要針對每一個目標對象創建一個代理對象,而是將代理對象的創建時機推遲到程序運行時交由JVM完成
3.3.1 JDK動態代理
JDK動態代理是Java標準庫提供的方式,要求目標對象必須實現接口。通過java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口動態生成代理類,生成的代理對象和目標對象實現自同一接口(而非與目標類本身有直接繼承關係),與上述靜態代理類似,但把代理對象的生成時機推遲到程序運行時
Proxy.newProxyInstance()是Java動態代理的核心方法,用於在運行時創建代理對象。該方法接收三個參數:
- ClassLoader loader:用於加載代理類的類加載器。通常傳入目標類的類加載器,確保代理類與目標類在同一個類加載器環境中
- 代理對象需要實現目標接口,其字節碼由Proxy工具類動態生成。通過目標類加載器創建代理類,可保證類型系統的一致性
- Class<?>[] interfaces:目標對象實現的接口數組。代理類會實現這些接口,並將方法調用轉發到InvocationHandler
- InvocationHandler h:負責處理代理對象的方法調用
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {
private final Object realHouse;
public JDKInvocationHandler(Object realHouse) {
this.realHouse = realHouse;
}
@Override
//Object proxy:生成的代理對象實例,Method method:被調用的目標方法對象,Object[] args:調用目標方法時傳入的參數數組
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.info("JDK開始代理");
//調用realHouse的方法
Object invoke = method.invoke(realHouse, args);
log.info("JDK結束代理,invoke:{}", invoke);
return invoke;
}
}
public class Main {
public static void main(String[] args) {
IHouse target = new RealHouse();
//JDK動態代理,只能代理接口
IHouse iHouse = (IHouse)Proxy.newProxyInstance(
RealHouse.class.getClassLoader(),
new Class[]{IHouse.class},
new JDKInvocationHandler(target)
);
iHouse.rent();
iHouse.sell();
}
}
運行結果:
3.3.2 CGLIB動態代理
CGLIB(Code Generation Library)是第三方庫,基於字節碼增強(動態生成目標類的子類字節碼,重寫方法邏輯),通過繼承方式實現代理,不要求目標對象實現接口。通過org.springframework.cglib.proxy.Enhancer類動態生成目標類的子類作為代理
//interface MethodInterceptor extends Callback
@Slf4j
public class CGLibMethodInterceptor implements MethodInterceptor {
private final Object realHouse;
public CGLibMethodInterceptor(Object realHouse) {
this.realHouse = realHouse;
}
@Override
//Object obj:動態生成的代理對象實例,Method method:當前被攔截的目標方法對象,Object[] args:方法調用時傳入的參數數組,MethodProxy proxy:方法代理對象
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
log.info("開始代理");
IHouse result = (IHouse)method.invoke(realHouse, args);
log.info("結束代理");
return result;
}
}
public class Main {
public static void main(String[] args) {
RealHouse target = new RealHouse();
RealHouse realHouse = (RealHouse)Enhancer.create(
RealHouse.class,
new CGLibMethodInterceptor(target)
);
realHouse.rent();
realHouse.sell();
}
}
執行結果: