最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入探索Spring AOP從原理到實(shí)戰(zhàn)操作

 更新時(shí)間:2025年12月01日 10:11:42   作者:CodeSuc  
本文詳細(xì)介紹了SpringAOP的基本概念、實(shí)現(xiàn)機(jī)制、核心術(shù)語(yǔ)及實(shí)戰(zhàn)應(yīng)用,SpringAOP通過(guò)動(dòng)態(tài)代理機(jī)制,在運(yùn)行時(shí)為目標(biāo)對(duì)象創(chuàng)建代理,實(shí)現(xiàn)對(duì)方法的增強(qiáng),本文探討了SpringAOP的高級(jí)特性,并列舉了日志記錄、事務(wù)管理、權(quán)限控制和性能監(jiān)控等應(yīng)用場(chǎng)景,感興趣的朋友跟隨小編一起看看吧

一、揭開(kāi) Spring AOP 的神秘面紗

1.1 什么是 AOP

在軟件編程的世界里,隨著系統(tǒng)規(guī)模的不斷擴(kuò)大和復(fù)雜性的日益增加,我們常常會(huì)遇到一些問(wèn)題,這些問(wèn)題涉及到多個(gè)模塊或類,卻又不屬于核心業(yè)務(wù)邏輯。比如日志記錄、事務(wù)管理、權(quán)限控制等功能,它們分散在各個(gè)業(yè)務(wù)代碼中,導(dǎo)致代碼的重復(fù)和臃腫,維護(hù)起來(lái)也變得異常困難。為了解決這些問(wèn)題,AOP 應(yīng)運(yùn)而生。

AOP,即 Aspect-Oriented Programming,面向切面編程,是一種編程范式,它的核心思想是將橫切關(guān)注點(diǎn)(cross-cutting concerns)從核心業(yè)務(wù)邏輯中分離出來(lái),形成一個(gè)個(gè)獨(dú)立的切面(Aspect)。橫切關(guān)注點(diǎn)就像是一條貫穿多個(gè)模塊的 “橫線”,這些功能在多個(gè)地方都需要用到,但又和具體的業(yè)務(wù)邏輯沒(méi)有直接關(guān)系。通過(guò) AOP,我們可以將這些橫切關(guān)注點(diǎn)模塊化,以一種聲明式的方式將它們應(yīng)用到需要的地方,從而實(shí)現(xiàn)代碼的解耦和復(fù)用,提高代碼的可維護(hù)性和可擴(kuò)展性。

AOP 并不是要取代面向?qū)ο缶幊蹋∣OP),而是對(duì) OOP 的一種補(bǔ)充和完善。OOP 主要關(guān)注的是對(duì)象的封裝、繼承和多態(tài),通過(guò)類和對(duì)象來(lái)組織代碼,解決的是業(yè)務(wù)邏輯的縱向劃分問(wèn)題;而 AOP 則關(guān)注的是那些橫跨多個(gè)類的行為,通過(guò)切面來(lái)模塊化這些橫切關(guān)注點(diǎn),解決的是業(yè)務(wù)邏輯的橫向抽取問(wèn)題。兩者相互配合,可以讓我們的代碼更加清晰、靈活和易于維護(hù)。

1.2 Spring AOP 是什么

Spring AOP 是 Spring 框架的一個(gè)重要組成部分,它基于 AOP 思想,提供了一種強(qiáng)大的功能,使得我們可以在 Spring 應(yīng)用中輕松地實(shí)現(xiàn)橫切關(guān)注點(diǎn)的分離和模塊化。Spring AOP 主要通過(guò)動(dòng)態(tài)代理機(jī)制來(lái)實(shí)現(xiàn),它允許我們?cè)谶\(yùn)行時(shí)為目標(biāo)對(duì)象創(chuàng)建代理對(duì)象,在不修改目標(biāo)對(duì)象源代碼的情況下,對(duì)其方法進(jìn)行增強(qiáng)。

在 Spring AOP 中,我們可以通過(guò)配置或注解的方式定義切面、切點(diǎn)和通知。切面是橫切關(guān)注點(diǎn)的模塊化封裝,它包含了切點(diǎn)和通知的定義;切點(diǎn)用于指定在哪些連接點(diǎn)上應(yīng)用切面的通知,連接點(diǎn)是程序執(zhí)行過(guò)程中的一個(gè)特定點(diǎn),比如方法調(diào)用、異常拋出等;通知?jiǎng)t是切面在特定連接點(diǎn)上執(zhí)行的代碼,它可以在方法執(zhí)行前、執(zhí)行后、返回結(jié)果后或拋出異常時(shí)執(zhí)行,從而實(shí)現(xiàn)對(duì)目標(biāo)方法的各種增強(qiáng)操作。

1.3 AOP 的核心術(shù)語(yǔ)

  • 通知(Advice):通知是切面在特定連接點(diǎn)執(zhí)行的操作,它定義了 “何時(shí)” 和 “做什么”。根據(jù)執(zhí)行時(shí)機(jī)的不同,通知可以分為以下幾種類型:
    • 前置通知(Before Advice):在目標(biāo)方法執(zhí)行之前執(zhí)行,比如在方法執(zhí)行前進(jìn)行日志記錄、權(quán)限檢查等操作。
    • 后置通知(After Advice):在目標(biāo)方法執(zhí)行之后執(zhí)行,無(wú)論方法是否正常返回或拋出異常,都會(huì)執(zhí)行后置通知,常用于資源清理等操作。
    • 返回通知(After Returning Advice):在目標(biāo)方法正常返回后執(zhí)行,它可以獲取到目標(biāo)方法的返回值,比如在方法返回后進(jìn)行結(jié)果處理、緩存設(shè)置等操作。
    • 異常通知(After Throwing Advice):在目標(biāo)方法拋出異常時(shí)執(zhí)行,它可以獲取到異常信息,常用于異常處理、日志記錄等操作。
    • 環(huán)繞通知(Around Advice):環(huán)繞通知是功能最強(qiáng)大的通知類型,它可以在目標(biāo)方法執(zhí)行前后都執(zhí)行操作,甚至可以決定是否執(zhí)行目標(biāo)方法。環(huán)繞通知需要手動(dòng)調(diào)用 ProceedingJoinPoint 的 proceed () 方法來(lái)執(zhí)行目標(biāo)方法,通過(guò)這種方式,我們可以實(shí)現(xiàn)對(duì)目標(biāo)方法的完全控制,比如在方法執(zhí)行前進(jìn)行參數(shù)校驗(yàn)、在方法執(zhí)行后進(jìn)行性能統(tǒng)計(jì)等操作。
  • 連接點(diǎn)(Join Point):連接點(diǎn)是程序執(zhí)行過(guò)程中的一個(gè)特定點(diǎn),比如方法調(diào)用、字段訪問(wèn)、構(gòu)造函數(shù)調(diào)用等。在 Spring AOP 中,連接點(diǎn)主要指方法的執(zhí)行,它是通知可以插入的位置。Spring AOP 通過(guò)動(dòng)態(tài)代理機(jī)制,在方法調(diào)用時(shí)攔截方法執(zhí)行,從而在連接點(diǎn)處織入通知。
  • 切點(diǎn)(Pointcut):切點(diǎn)是一組連接點(diǎn)的集合,它定義了哪些連接點(diǎn)會(huì)被切面所影響,也就是決定在哪些方法或函數(shù)上應(yīng)用通知。切點(diǎn)通過(guò)切點(diǎn)表達(dá)式來(lái)定義,切點(diǎn)表達(dá)式可以根據(jù)方法的簽名、參數(shù)、返回值、類名、包名等條件來(lái)匹配連接點(diǎn)。比如,我們可以定義一個(gè)切點(diǎn)表達(dá)式,匹配所有以 “save” 開(kāi)頭的方法,或者匹配所有標(biāo)注了特定注解的方法。通過(guò)切點(diǎn)的定義,我們可以精確地控制切面的作用范圍,只對(duì)需要的連接點(diǎn)應(yīng)用通知,從而避免對(duì)不需要的方法進(jìn)行不必要的增強(qiáng)。
  • 切面(Aspect):切面是通知和切點(diǎn)的結(jié)合體,它將橫切關(guān)注點(diǎn)封裝為一個(gè)可重用的模塊。一個(gè)切面可以包含多個(gè)切點(diǎn)和通知,通過(guò)切面的定義,我們可以將相關(guān)的橫切邏輯集中管理,提高代碼的模塊化程度和可維護(hù)性。例如,我們可以定義一個(gè)日志切面,它包含了在方法執(zhí)行前后記錄日志的通知,以及匹配所有業(yè)務(wù)方法的切點(diǎn),這樣就可以對(duì)所有業(yè)務(wù)方法進(jìn)行統(tǒng)一的日志記錄。
  • 引入(Introduction):引入允許我們?cè)谶\(yùn)行時(shí)為目標(biāo)對(duì)象動(dòng)態(tài)添加新的接口和實(shí)現(xiàn)。通過(guò)引入,我們可以為現(xiàn)有的類添加額外的功能,而不需要修改類的源代碼。比如,我們可以為一個(gè)類引入一個(gè)新的接口,使其具備某種新的行為,這種方式在擴(kuò)展現(xiàn)有類的功能時(shí)非常有用。
  • 織入(Weaving):織入是將切面應(yīng)用到目標(biāo)對(duì)象并創(chuàng)建代理對(duì)象的過(guò)程??椚肟梢栽诰幾g時(shí)、類加載時(shí)或運(yùn)行時(shí)進(jìn)行。在 Spring AOP 中,默認(rèn)采用運(yùn)行時(shí)織入的方式,通過(guò)動(dòng)態(tài)代理在運(yùn)行時(shí)為目標(biāo)對(duì)象創(chuàng)建代理對(duì)象,并將切面的通知織入到代理對(duì)象的方法調(diào)用中。在編譯時(shí)織入需要特殊的編譯器支持,類加載時(shí)織入則需要特殊的類加載器支持。
  • 目標(biāo)對(duì)象(Target Object):目標(biāo)對(duì)象是被切面增強(qiáng)的對(duì)象,也就是實(shí)際執(zhí)行業(yè)務(wù)邏輯的對(duì)象。在 Spring AOP 中,目標(biāo)對(duì)象通常是一個(gè)普通的 POJO(Plain Old Java Object),它不知道自己被代理和增強(qiáng)。
  • 代理對(duì)象(Proxy Object):代理對(duì)象是 Spring AOP 為目標(biāo)對(duì)象創(chuàng)建的代理實(shí)例,它負(fù)責(zé)在目標(biāo)對(duì)象的方法調(diào)用前后執(zhí)行切面的通知。代理對(duì)象實(shí)現(xiàn)了與目標(biāo)對(duì)象相同的接口(如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口),或者繼承了目標(biāo)對(duì)象(如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口)。當(dāng)我們調(diào)用代理對(duì)象的方法時(shí),實(shí)際上是調(diào)用代理對(duì)象的方法,代理對(duì)象會(huì)在方法調(diào)用前后執(zhí)行切面的通知,然后再調(diào)用目標(biāo)對(duì)象的方法。根據(jù)代理方式的不同,Spring AOP 提供了兩種代理對(duì)象:JDK 動(dòng)態(tài)代理和 CGLIB 代理。JDK 動(dòng)態(tài)代理基于 Java 反射機(jī)制,只能為實(shí)現(xiàn)了接口的目標(biāo)對(duì)象創(chuàng)建代理;CGLIB 代理基于字節(jié)碼生成技術(shù),可以為沒(méi)有實(shí)現(xiàn)接口的目標(biāo)對(duì)象創(chuàng)建代理。

二、Spring AOP 的底層基石:動(dòng)態(tài)代理

2.1 JDK 動(dòng)態(tài)代理

JDK 動(dòng)態(tài)代理是 Java 原生提供的一種動(dòng)態(tài)代理機(jī)制,它基于反射機(jī)制實(shí)現(xiàn),要求目標(biāo)對(duì)象必須實(shí)現(xiàn)至少一個(gè)接口。在 JDK 動(dòng)態(tài)代理中,核心的類和接口有兩個(gè):java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler。

Proxy類主要負(fù)責(zé)生成代理對(duì)象,它提供了一個(gè)靜態(tài)方法newProxyInstance,通過(guò)這個(gè)方法可以創(chuàng)建一個(gè)代理對(duì)象。該方法接受三個(gè)參數(shù):類加載器(ClassLoader)、目標(biāo)對(duì)象實(shí)現(xiàn)的接口數(shù)組(Class<?>[] interfaces)以及一個(gè)實(shí)現(xiàn)了InvocationHandler接口的處理器對(duì)象(InvocationHandler h)。

InvocationHandler接口則定義了代理對(duì)象的行為邏輯,它只有一個(gè)方法invoke,當(dāng)我們調(diào)用代理對(duì)象的方法時(shí),實(shí)際上會(huì)調(diào)用到這個(gè)invoke方法。在invoke方法中,我們可以在目標(biāo)方法執(zhí)行前后添加自定義的邏輯,比如日志記錄、事務(wù)管理等,然后通過(guò)反射調(diào)用目標(biāo)對(duì)象的實(shí)際方法。

下面通過(guò)一個(gè)簡(jiǎn)單的代碼示例來(lái)演示 JDK 動(dòng)態(tài)代理的使用:

// 定義一個(gè)接口
interface UserService {
    void addUser(String username);
}
// 實(shí)現(xiàn)接口的目標(biāo)類
class UserServiceImpl implements UserService {
    @Override
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }
}
// 實(shí)現(xiàn)InvocationHandler接口
class MyInvocationHandler implements InvocationHandler {
    private Object target;
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method execution");
        Object result = method.invoke(target, args);
        System.out.println("After method execution");
        return result;
    }
}
public class Main {
    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        InvocationHandler handler = new MyInvocationHandler(target);
        UserService proxy = (UserService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                handler
        );
        proxy.addUser("Alice");
    }
}

在上述代碼中,首先定義了一個(gè)UserService接口和實(shí)現(xiàn)該接口的UserServiceImpl類。然后創(chuàng)建了一個(gè)MyInvocationHandler類,實(shí)現(xiàn)了InvocationHandler接口,在invoke方法中添加了前置和后置的打印邏輯。最后在main方法中,通過(guò)Proxy.newProxyInstance方法創(chuàng)建了代理對(duì)象,并調(diào)用代理對(duì)象的addUser方法。運(yùn)行代碼后,可以看到在目標(biāo)方法執(zhí)行前后分別打印了 “Before method execution” 和 “After method execution”。

2.2 Cglib 動(dòng)態(tài)代理

Cglib(Code Generation Library)是一個(gè)高性能的代碼生成庫(kù),它通過(guò)字節(jié)碼操作技術(shù)在運(yùn)行時(shí)動(dòng)態(tài)生成目標(biāo)類的子類,從而實(shí)現(xiàn)代理功能。與 JDK 動(dòng)態(tài)代理不同,Cglib 代理不需要目標(biāo)類實(shí)現(xiàn)接口,因此可以代理普通的類。

Cglib 動(dòng)態(tài)代理的核心類是net.sf.cglib.proxy.Enhancernet.sf.cglib.proxy.MethodInterceptor。Enhancer類用于生成代理類,它提供了一系列方法來(lái)配置代理類的屬性,比如設(shè)置父類(即目標(biāo)類)、設(shè)置回調(diào)函數(shù)等。MethodInterceptor接口則用于定義方法的攔截邏輯,當(dāng)調(diào)用代理對(duì)象的方法時(shí),會(huì)觸發(fā)MethodInterceptorintercept方法,在這個(gè)方法中我們可以實(shí)現(xiàn)對(duì)目標(biāo)方法的增強(qiáng)。

下面是一個(gè)使用 Cglib 動(dòng)態(tài)代理的代碼示例:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
class UserService {
    public void addUser(String username) {
        System.out.println("Adding user: " + username);
    }
}
class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method execution");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method execution");
        return result;
    }
}
public class CglibProxyExample {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new MyMethodInterceptor());
        UserService proxy = (UserService) enhancer.create();
        proxy.addUser("Bob");
    }
}

在這個(gè)示例中,首先定義了一個(gè)沒(méi)有實(shí)現(xiàn)接口的UserService類。然后創(chuàng)建了MyMethodInterceptor類,實(shí)現(xiàn)了MethodInterceptor接口,在intercept方法中添加了前置和后置的打印邏輯。最后在main方法中,通過(guò)Enhancer類設(shè)置目標(biāo)類為UserService,并設(shè)置回調(diào)函數(shù)為MyMethodInterceptor,調(diào)用create方法生成代理對(duì)象,并調(diào)用代理對(duì)象的addUser方法。運(yùn)行代碼后,同樣可以看到在目標(biāo)方法執(zhí)行前后打印了相應(yīng)的信息。

2.3 兩者對(duì)比與 Spring 的選擇

  • 性能對(duì)比
    • JDK 動(dòng)態(tài)代理:由于它基于反射機(jī)制,在創(chuàng)建代理對(duì)象和方法調(diào)用時(shí)會(huì)涉及到反射操作,因此性能相對(duì)較低。特別是在方法調(diào)用頻繁的情況下,反射帶來(lái)的開(kāi)銷會(huì)比較明顯。不過(guò),在代理對(duì)象創(chuàng)建較少且方法調(diào)用次數(shù)不多的場(chǎng)景下,其性能表現(xiàn)還是可以接受的。
    • Cglib 動(dòng)態(tài)代理:Cglib 通過(guò)字節(jié)碼生成技術(shù),直接在運(yùn)行時(shí)生成目標(biāo)類的子類,方法調(diào)用時(shí)直接調(diào)用子類的方法,避免了反射的開(kāi)銷,因此在性能上通常優(yōu)于 JDK 動(dòng)態(tài)代理,尤其是在大量創(chuàng)建代理對(duì)象和頻繁調(diào)用方法的場(chǎng)景下,Cglib 的優(yōu)勢(shì)更加明顯。
  • 適用場(chǎng)景對(duì)比
    • JDK 動(dòng)態(tài)代理:適用于目標(biāo)類已經(jīng)實(shí)現(xiàn)了接口的情況,因?yàn)樗仨氁蕾嚱涌趤?lái)創(chuàng)建代理對(duì)象。在 Java 的企業(yè)級(jí)開(kāi)發(fā)中,很多服務(wù)層接口都采用這種方式,所以 JDK 動(dòng)態(tài)代理在 Spring 的 AOP 中也被廣泛應(yīng)用于代理有接口的服務(wù)。
    • Cglib 動(dòng)態(tài)代理:適用于目標(biāo)類沒(méi)有實(shí)現(xiàn)接口,或者需要代理類的所有方法(包括非接口方法)的場(chǎng)景。例如,當(dāng)我們需要對(duì)一些第三方庫(kù)中的類進(jìn)行增強(qiáng),而這些類沒(méi)有實(shí)現(xiàn)特定接口時(shí),Cglib 就可以發(fā)揮作用。此外,由于 Cglib 的性能優(yōu)勢(shì),對(duì)于一些對(duì)性能要求較高且目標(biāo)類無(wú)接口的場(chǎng)景,也可以優(yōu)先考慮使用 Cglib。
  • Spring 的選擇策略
    • 在 Spring AOP 中,默認(rèn)情況下,如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,Spring 會(huì)優(yōu)先使用 JDK 動(dòng)態(tài)代理來(lái)創(chuàng)建代理對(duì)象;如果目標(biāo)對(duì)象沒(méi)有實(shí)現(xiàn)接口,Spring 則會(huì)使用 Cglib 動(dòng)態(tài)代理。
    • 不過(guò),Spring 也提供了配置選項(xiàng),允許我們手動(dòng)指定使用哪種代理方式。例如,通過(guò)在配置文件中設(shè)置proxy-target-class="true",可以強(qiáng)制 Spring 使用 Cglib 代理,即使目標(biāo)對(duì)象實(shí)現(xiàn)了接口。這種配置在一些特殊場(chǎng)景下非常有用,比如當(dāng)我們需要代理類的所有方法,包括那些在接口中沒(méi)有定義的方法時(shí)。

三、Spring AOP 實(shí)戰(zhàn)演練

3.1 環(huán)境搭建

在開(kāi)始使用 Spring AOP 之前,我們需要搭建一個(gè) Spring 項(xiàng)目,并引入 Spring AOP 的相關(guān)依賴。如果你使用的是 Maven 項(xiàng)目管理工具,可以在pom.xml文件中添加以下依賴:

<dependencies>
    <!-- Spring Core -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!-- Spring AOP -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.23</version>
    </dependency>
    <!-- AspectJ Weaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.9.1</version>
    </dependency>
</dependencies>

上述配置中,spring-context是 Spring 的核心依賴,spring-aop提供了 Spring AOP 的支持,aspectjweaver是 AspectJ 的織入器,Spring AOP 默認(rèn)使用 AspectJ 的切點(diǎn)表達(dá)式語(yǔ)言,因此需要引入這個(gè)依賴。

如果你使用的是 Gradle,可以在build.gradle文件中添加以下依賴:

dependencies {
    implementation 'org.springframework:spring-context:5.3.23'
    implementation 'org.springframework:spring-aop:5.3.23'
    implementation 'org.aspectj:aspectjweaver:1.9.9.1'
}

添加完依賴后,Maven 或 Gradle 會(huì)自動(dòng)下載并管理這些依賴。

3.2 定義切面

在 Spring AOP 中,我們可以使用注解或 XML 配置的方式來(lái)定義切面。下面先介紹使用注解定義切面的方法:
首先,創(chuàng)建一個(gè)切面類,并使用@Aspect注解標(biāo)記它,表明這是一個(gè)切面。同時(shí),使用@Component注解將其注冊(cè)為 Spring 容器中的一個(gè) Bean,這樣 Spring 才能管理它。例如:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    // 定義切點(diǎn)表達(dá)式,匹配com.example.service包下所有類的所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    // 其他通知方法將在這里定義
}

在上述代碼中,@Pointcut注解定義了一個(gè)切點(diǎn),切點(diǎn)表達(dá)式execution(* com.example.service.*.*(..))表示匹配com.example.service包下所有類的所有方法。serviceMethods方法本身沒(méi)有實(shí)際的業(yè)務(wù)邏輯,它只是一個(gè)標(biāo)識(shí),用于在后續(xù)的通知中引用這個(gè)切點(diǎn)。

接下來(lái)介紹使用 XML 配置定義切面的方法:
首先,在 Spring 的配置文件(如applicationContext.xml)中,需要引入 AOP 的命名空間:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">

然后,定義切面類,并將其配置為 Spring 容器中的 Bean:

<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>

接著,定義切點(diǎn)和通知:

<aop:config>
    <aop:aspect ref="loggingAspect">
        <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
        <!-- 其他通知配置將在這里定義 -->
    </aop:aspect>
</aop:config>

在上述 XML 配置中,<aop:config>標(biāo)簽用于配置 AOP 相關(guān)的內(nèi)容,<aop:aspect>標(biāo)簽引用了切面類loggingAspect<aop:pointcut>標(biāo)簽定義了切點(diǎn),表達(dá)式與注解方式中的切點(diǎn)表達(dá)式相同。

3.3 通知類型詳解

  • 前置通知(Before Advice)
    • 執(zhí)行時(shí)機(jī):在目標(biāo)方法執(zhí)行之前執(zhí)行。
    • 使用場(chǎng)景:常用于權(quán)限檢查、日志記錄等操作。例如,在方法執(zhí)行前檢查用戶是否有權(quán)限執(zhí)行該方法,或者記錄方法的調(diào)用信息。
    • 注解方式示例
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.info("方法 {} 開(kāi)始執(zhí)行,參數(shù): {}", methodName, args);
    }
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
}

在上述代碼中,@Before注解表示這是一個(gè)前置通知,serviceMethods()是前面定義的切點(diǎn),表示這個(gè)通知應(yīng)用于com.example.service包下所有類的所有方法。logBefore方法中的JoinPoint參數(shù)可以獲取到目標(biāo)方法的簽名和參數(shù)等信息。
2. 后置通知(After Advice)

  • 執(zhí)行時(shí)機(jī):在目標(biāo)方法執(zhí)行之后執(zhí)行,無(wú)論目標(biāo)方法是否正常返回或拋出異常。
  • 使用場(chǎng)景:常用于資源清理等操作。例如,在方法執(zhí)行后關(guān)閉數(shù)據(jù)庫(kù)連接、釋放文件資源等。
  • 注解方式示例
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("方法 {} 執(zhí)行結(jié)束", methodName);
    }
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
}
  • 返回通知(After Returning Advice)
    • 執(zhí)行時(shí)機(jī):在目標(biāo)方法正常返回后執(zhí)行。
    • 使用場(chǎng)景:常用于對(duì)方法返回結(jié)果進(jìn)行處理。例如,對(duì)返回結(jié)果進(jìn)行緩存、格式化等操作。
    • 注解方式示例
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @AfterReturning(pointcut = "serviceMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        logger.info("方法 {} 返回結(jié)果: {}", methodName, result);
    }
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
}

在上述代碼中,@AfterReturning注解的returning屬性指定了一個(gè)參數(shù)名result,這個(gè)參數(shù)名與通知方法中的參數(shù)名相對(duì)應(yīng),用于接收目標(biāo)方法的返回值。
4. 異常通知(After Throwing Advice)

  • 執(zhí)行時(shí)機(jī):在目標(biāo)方法拋出異常時(shí)執(zhí)行。
  • 使用場(chǎng)景:常用于異常處理、日志記錄等操作。例如,在方法拋出異常時(shí)記錄異常信息,或者進(jìn)行統(tǒng)一的異常處理。
  • 注解方式示例
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        logger.error("方法 {} 拋出異常: {}", methodName, ex.getMessage());
    }
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
}

在上述代碼中,@AfterThrowing注解的throwing屬性指定了一個(gè)參數(shù)名ex,用于接收目標(biāo)方法拋出的異常對(duì)象。
5. 環(huán)繞通知(Around Advice)

  • 執(zhí)行時(shí)機(jī):在目標(biāo)方法執(zhí)行前后都執(zhí)行,可以完全控制目標(biāo)方法的執(zhí)行。
  • 使用場(chǎng)景:常用于性能統(tǒng)計(jì)、事務(wù)管理等操作。例如,統(tǒng)計(jì)方法的執(zhí)行時(shí)間,或者在方法執(zhí)行前后開(kāi)啟和提交事務(wù)。
  • 注解方式示例
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @Around("serviceMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        logger.info("方法 {} 開(kāi)始執(zhí)行", joinPoint.getSignature().getName());
        try {
            return joinPoint.proceed();
        } finally {
            long endTime = System.currentTimeMillis();
            logger.info("方法 {} 執(zhí)行結(jié)束,耗時(shí): {} ms", joinPoint.getSignature().getName(), endTime - startTime);
        }
    }
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
}

在上述代碼中,@Around注解的通知方法接受一個(gè)ProceedingJoinPoint參數(shù),通過(guò)調(diào)用它的proceed()方法來(lái)執(zhí)行目標(biāo)方法。在調(diào)用proceed()方法前后添加的代碼,分別在目標(biāo)方法執(zhí)行前和執(zhí)行后執(zhí)行。

3.4 切點(diǎn)表達(dá)式

切點(diǎn)表達(dá)式是 Spring AOP 中非常重要的一部分,它用于指定哪些方法會(huì)被切面所影響。Spring AOP 默認(rèn)使用 AspectJ 的切點(diǎn)表達(dá)式語(yǔ)言,下面詳細(xì)介紹其語(yǔ)法規(guī)則:

  1. 基本語(yǔ)法結(jié)構(gòu)
execution([修飾符模式] 返回值類型模式 [類名模式]方法名模式(參數(shù)模式)[異常模式])

其中,方括號(hào)內(nèi)的部分是可選的。

  • 修飾符模式:用于匹配方法的修飾符,如public、private、protected等??梢允褂猛ㄅ浞?code>*匹配任意修飾符,通常情況下修飾符模式可以省略。
  • 返回值類型模式:用于匹配方法的返回值類型,*表示匹配任意返回值類型,也可以指定具體的返回值類型,如voidint、String等。
  • 類名模式:用于匹配類名,可以指定包名和類名相關(guān)信息。包名后若跟..表示當(dāng)前包及其子包;類名用*表示匹配所有類。例如,com.example.service..*表示com.example.service包及其子包下的所有類。
  • 方法名模式*表示匹配所有方法名;也可指定特定方法名,如get*表示以get開(kāi)頭的方法。
  • 參數(shù)模式(..)表示匹配任意參數(shù)數(shù)量和類型;也可具體指定,如(int, String)表示方法有兩個(gè)參數(shù),分別為int類型和String類型 ,(int,..) 表示第一個(gè)參數(shù)是int類型,后面可以跟任意數(shù)量和類型參數(shù)。
  • 異常模式:用于匹配方法拋出的異常類型,較少使用,通常省略。
  • 通配符詳解
    • *:匹配任意字符(除.外)。例如,com.*.service表示com包下任意子包下的service包。
    • ..:匹配任意子包或多個(gè)參數(shù)。在包名中使用時(shí),表示當(dāng)前包及其子包;在參數(shù)模式中使用時(shí),表示任意數(shù)量和類型的參數(shù)。例如,com.example..*Service表示com.example包及其子包下所有以Service結(jié)尾的類;(..)表示任意參數(shù)。
    • +:匹配指定類型及其子類。例如,java.util.List+表示匹配java.util.List接口及其所有實(shí)現(xiàn)類。
  • 常用示例
  • 匹配指定包下所有類的所有方法execution(* com.example.dao..*.*(..)),表示匹配com.example.dao包及其子包下所有類的所有方法,第一個(gè)*匹配任意返回值類型,第二個(gè)*匹配所有類,第三個(gè)* 匹配所有方法名,(..)匹配任意參數(shù)。
  • 匹配指定類的所有方法execution(* com.example.service.UserService.*(..)),即匹配com.example.service.UserService類的所有方法 。
  • 匹配指定接口所有實(shí)現(xiàn)類的方法execution(* com.example.dao.GenericDAO+.*(..)),+表示匹配GenericDAO接口的所有實(shí)現(xiàn)類的方法。
  • 匹配特定方法名開(kāi)頭的方法execution(* save*(..)),表示匹配所有以save開(kāi)頭的方法,不限定返回值類型、類和參數(shù)。
  • 多表達(dá)式組合
    • 多個(gè) execution 表達(dá)式之間可以通過(guò)邏輯運(yùn)算符組合:
    • 或(|| 或 or ):表示滿足其中一個(gè)表達(dá)式即可。例如,execution(* com.example.service.UserService.add(..)) || execution(* com.example.service.UserService.delete(..)),表示匹配UserService類中的add方法或者delete方法。
    • 與(&& 或 and ):要求同時(shí)滿足多個(gè)表達(dá)式。例如,execution(* com.example.service..*.*(..)) && args(String) ,表示匹配com.example.service包及其子包下所有類的方法,且方法參數(shù)包含String類型 。
    • 非(! 或 not ):對(duì)表達(dá)式取反。例如,!execution(* com.example.service.UserService.get*(..)) ,表示匹配除了UserService類中以get開(kāi)頭方法之外的其他方法。
  • 其他切點(diǎn)指示符
    • within 表達(dá)式:主要用于根據(jù)類型(類或包)匹配連接點(diǎn)。它更側(cè)重類或包的范圍匹配,而execution更側(cè)重方法簽名匹配。
    • 匹配指定包下的所有類:語(yǔ)法為within(包名..*),其中..表示當(dāng)前包及其子包。例如,within(com.example.service..*)表示匹配com.example.service包及其子包下所有類的所有方法。
    • 匹配指定類:語(yǔ)法為within(全限定類名)。例如,within(com.example.service.UserService)表示只匹配com.example.service.UserService類的所有方法。
    • @annotation 表達(dá)式:根據(jù)方法上是否存在特定注解來(lái)匹配連接點(diǎn)。當(dāng)某方法標(biāo)注指定注解,該方法執(zhí)行就觸發(fā)相應(yīng)切面邏輯。
    • 自定義注解:首先定義一個(gè)自定義注解,例如:
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 Loggable {}
  • 用 @annotation 匹配帶注解的方法:語(yǔ)法為@annotation(注解全限定名)。例如,@annotation(com.example.annotation.Loggable)表示匹配所有帶有@Loggable注解的方法。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    @Before("@annotation(com.example.annotation.Loggable)")
    public void beforeLoggableMethod(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }
}
  • 在方法上使用注解:在需要增強(qiáng)的方法上添加自定義注解,例如:
import com.example.annotation.Loggable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
    @Loggable
    public void saveUser() {
        System.out.println("Saving user...");
    }
}

當(dāng)UserService#saveUser

四、Spring AOP 的高級(jí)特性與應(yīng)用場(chǎng)景

4.1 引入(Introduction)

引入是 Spring AOP 的一個(gè)高級(jí)特性,它允許我們?cè)谶\(yùn)行時(shí)為目標(biāo)對(duì)象動(dòng)態(tài)添加新的接口和實(shí)現(xiàn),而不需要修改目標(biāo)對(duì)象的源代碼。這一特性在擴(kuò)展現(xiàn)有類的功能時(shí)非常有用,比如我們可以為一個(gè)現(xiàn)有的類添加日志記錄、緩存等功能,而無(wú)需對(duì)該類進(jìn)行任何改動(dòng)。

在 Spring AOP 中,使用引入功能需要定義一個(gè)切面,并在切面中使用@DeclareParents注解。@DeclareParents注解有三個(gè)屬性:value指定要引入接口的目標(biāo)類或類的集合,使用切點(diǎn)表達(dá)式來(lái)表示;defaultImpl指定接口的默認(rèn)實(shí)現(xiàn)類;defaultAnnotation指定一個(gè)可選的注解,用于標(biāo)記哪些目標(biāo)對(duì)象應(yīng)該引入該接口(通常較少使用)。

下面通過(guò)一個(gè)具體的代碼示例來(lái)演示引入的使用:
首先,定義一個(gè)要引入的接口及其實(shí)現(xiàn)類:

// 定義要引入的接口
public interface Cacheable {
    void cache();
}
// 接口的實(shí)現(xiàn)類
public class CacheableImpl implements Cacheable {
    @Override
    public void cache() {
        System.out.println("Caching data...");
    }
}

然后,定義一個(gè)切面類,使用@DeclareParents注解將Cacheable接口引入到目標(biāo)類:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CacheAspect {
    @DeclareParents(value = "com.example.service.*Service+", defaultImpl = CacheableImpl.class)
    public static Cacheable cacheable;
}

在上述代碼中,@DeclareParents注解的value屬性使用切點(diǎn)表達(dá)式com.example.service.*Service+表示com.example.service包下所有以Service結(jié)尾的類及其子類,defaultImpl屬性指定了Cacheable接口的默認(rèn)實(shí)現(xiàn)類為CacheableImpl。

最后,在使用時(shí),可以通過(guò)類型轉(zhuǎn)換將目標(biāo)對(duì)象轉(zhuǎn)換為引入的接口類型,從而調(diào)用引入的方法:

import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
    @Autowired
    private UserService userService;
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Override
    public void run(String... args) throws Exception {
        Cacheable cacheable = (Cacheable) userService;
        cacheable.cache();
    }
}

在上述代碼中,將UserService對(duì)象轉(zhuǎn)換為Cacheable類型,然后調(diào)用cache方法,就可以執(zhí)行引入的緩存功能。通過(guò)引入,我們可以在不修改UserService類的情況下,為其添加新的功能,這大大提高了代碼的可維護(hù)性和擴(kuò)展性。

4.2 多切面與切面優(yōu)先級(jí)

在實(shí)際應(yīng)用中,可能會(huì)存在多個(gè)切面作用于同一個(gè)目標(biāo)對(duì)象的情況。當(dāng)多個(gè)切面作用于同一目標(biāo)對(duì)象時(shí),它們的執(zhí)行順序是有規(guī)則的。默認(rèn)情況下,Spring AOP 會(huì)按照切面類名的字母順序來(lái)決定切面的執(zhí)行順序,類名靠前的切面先執(zhí)行。但這種默認(rèn)的順序在很多情況下并不能滿足我們的需求,因此 Spring 提供了設(shè)置切面優(yōu)先級(jí)的機(jī)制。

在 Spring AOP 中,可以通過(guò)實(shí)現(xiàn)Ordered接口或使用@Order注解來(lái)設(shè)置切面的優(yōu)先級(jí)。Ordered接口只有一個(gè)方法getOrder,返回值越小,表示優(yōu)先級(jí)越高。@Order注解的使用則更加簡(jiǎn)潔,它可以直接標(biāo)注在切面類上,其參數(shù)值越小,優(yōu)先級(jí)越高。

下面通過(guò)代碼示例來(lái)演示如何設(shè)置切面優(yōu)先級(jí):
首先,定義兩個(gè)切面類:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Order(1) // 設(shè)置優(yōu)先級(jí)為1,值越小優(yōu)先級(jí)越高
public class HighPriorityAspect {
    @Before("execution(* com.example.service.*.*(..))")
    public void highPriorityBefore() {
        System.out.println("高優(yōu)先級(jí)切面的前置通知");
    }
}
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LowPriorityAspect implements Ordered {
    @Before("execution(* com.example.service.*.*(..))")
    public void lowPriorityBefore() {
        System.out.println("低優(yōu)先級(jí)切面的前置通知");
    }
    @Override
    public int getOrder() {
        return 2; // 設(shè)置優(yōu)先級(jí)為2
    }
}

在上述代碼中,HighPriorityAspect使用@Order(1)注解設(shè)置優(yōu)先級(jí)為 1,LowPriorityAspect實(shí)現(xiàn)Ordered接口,通過(guò)getOrder方法返回 2 來(lái)設(shè)置優(yōu)先級(jí)為 2。因此,在目標(biāo)方法執(zhí)行前,HighPriorityAspect的前置通知會(huì)先執(zhí)行,然后才執(zhí)行LowPriorityAspect的前置通知。

4.3 應(yīng)用場(chǎng)景舉例

  1. 日志記錄:在企業(yè)級(jí)應(yīng)用中,我們通常需要記錄方法的調(diào)用情況,包括方法的入?yún)?、返回值、?zhí)行時(shí)間等信息。使用 Spring AOP 可以輕松實(shí)現(xiàn)這一需求。通過(guò)定義一個(gè)日志切面,使用切點(diǎn)表達(dá)式匹配所有需要記錄日志的方法,然后在前置通知中記錄方法的入?yún)⒑烷_(kāi)始時(shí)間,在返回通知中記錄返回值和執(zhí)行時(shí)間,在異常通知中記錄異常信息。這樣,所有被匹配的方法在執(zhí)行時(shí)都會(huì)自動(dòng)記錄日志,避免了在每個(gè)方法中手動(dòng)添加日志代碼,提高了代碼的可維護(hù)性和可讀性。例如:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        logger.info("開(kāi)始執(zhí)行方法 {},參數(shù): {}", methodName, args);
        try {
            Object result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            logger.info("方法 {} 執(zhí)行完畢,返回值: {},執(zhí)行時(shí)間: {} ms", methodName, result, endTime - startTime);
            return result;
        } catch (Exception e) {
            logger.error("方法 {} 執(zhí)行出錯(cuò): {}", methodName, e.getMessage());
            throw e;
        }
    }
}
  1. 事務(wù)管理:在涉及數(shù)據(jù)庫(kù)操作的應(yīng)用中,事務(wù)管理是非常重要的。Spring AOP 可以將事務(wù)管理的邏輯從業(yè)務(wù)代碼中分離出來(lái),實(shí)現(xiàn)聲明式事務(wù)管理。通過(guò)定義一個(gè)事務(wù)切面,使用切點(diǎn)表達(dá)式匹配需要進(jìn)行事務(wù)管理的方法,然后在環(huán)繞通知中開(kāi)啟事務(wù)、執(zhí)行業(yè)務(wù)方法、根據(jù)執(zhí)行結(jié)果提交或回滾事務(wù)。這樣,業(yè)務(wù)代碼中就不需要顯式地編寫事務(wù)相關(guān)的代碼,只需要在配置文件或注解中聲明事務(wù)的屬性,如事務(wù)的傳播行為、隔離級(jí)別等,提高了代碼的簡(jiǎn)潔性和事務(wù)管理的一致性。例如:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TransactionAspect {
    private final DataSourceTransactionManager transactionManager;
    public TransactionAspect(DataSourceTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    @Around("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
    public Object transactionAround(ProceedingJoinPoint joinPoint) throws Throwable {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            Object result = joinPoint.proceed();
            transactionManager.commit(status);
            return result;
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}
  1. 權(quán)限控制:在 Web 應(yīng)用中,我們需要對(duì)用戶的操作進(jìn)行權(quán)限控制,確保只有具備相應(yīng)權(quán)限的用戶才能訪問(wèn)特定的資源或執(zhí)行特定的操作。Spring AOP 可以將權(quán)限控制的邏輯從業(yè)務(wù)代碼中分離出來(lái),實(shí)現(xiàn)統(tǒng)一的權(quán)限管理。通過(guò)定義一個(gè)權(quán)限切面,使用切點(diǎn)表達(dá)式匹配需要進(jìn)行權(quán)限控制的方法,然后在前置通知中檢查用戶的權(quán)限,根據(jù)權(quán)限檢查結(jié)果決定是否允許方法執(zhí)行。這樣,業(yè)務(wù)代碼中就不需要重復(fù)編寫權(quán)限檢查的代碼,提高了代碼的安全性和可維護(hù)性。例如:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PermissionAspect {
    @Around("execution(* com.example.controller.*.*(..))")
    public Object permissionAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.isAuthenticated()) {
            // 檢查用戶權(quán)限,這里只是示例,實(shí)際應(yīng)用中需要根據(jù)具體的權(quán)限模型進(jìn)行檢查
            String username = authentication.getName();
            if ("admin".equals(username)) {
                return joinPoint.proceed();
            }
        }
        throw new RuntimeException("沒(méi)有權(quán)限訪問(wèn)該資源");
    }
}
  1. 性能監(jiān)控:在應(yīng)用性能優(yōu)化過(guò)程中,我們需要了解各個(gè)方法的執(zhí)行時(shí)間,找出性能瓶頸。Spring AOP 可以幫助我們實(shí)現(xiàn)方法執(zhí)行時(shí)間的監(jiān)控。通過(guò)定義一個(gè)性能監(jiān)控切面,使用切點(diǎn)表達(dá)式匹配需要監(jiān)控的方法,在環(huán)繞通知中記錄方法的開(kāi)始時(shí)間和結(jié)束時(shí)間,計(jì)算并輸出方法的執(zhí)行耗時(shí)。這樣,我們可以方便地對(duì)系統(tǒng)中各個(gè)方法的性能進(jìn)行分析,從而有針對(duì)性地進(jìn)行優(yōu)化。例如:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PerformanceMonitorAspect {
    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorAspect.class);
    @Around("execution(* com.example.service.*.*(..))")
    public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        long executionTime = endTime - startTime;
        logger.info("方法 {} 執(zhí)行耗時(shí): {} ms", joinPoint.getSignature().getName(), executionTime);
        return result;
    }
}

五、總結(jié)與展望

5.1 知識(shí)點(diǎn)回顧

在本文中,我們深入探索了 Spring AOP 這一強(qiáng)大的技術(shù)領(lǐng)域。開(kāi)篇我們闡述了 AOP 的基本概念,它作為一種編程范式,將橫切關(guān)注點(diǎn)從核心業(yè)務(wù)邏輯中分離,極大地提升了代碼的可維護(hù)性與復(fù)用性。Spring AOP 則是基于 AOP 思想在 Spring 框架中的具體實(shí)現(xiàn),通過(guò)動(dòng)態(tài)代理機(jī)制,在運(yùn)行時(shí)為目標(biāo)對(duì)象創(chuàng)建代理,實(shí)現(xiàn)對(duì)方法的增強(qiáng)。

接著,我們?cè)敿?xì)剖析了 Spring AOP 底層依賴的動(dòng)態(tài)代理機(jī)制,包括 JDK 動(dòng)態(tài)代理和 Cglib 動(dòng)態(tài)代理。JDK 動(dòng)態(tài)代理基于反射,要求目標(biāo)對(duì)象實(shí)現(xiàn)接口;Cglib 動(dòng)態(tài)代理通過(guò)字節(jié)碼生成技術(shù),可代理普通類。Spring 會(huì)根據(jù)目標(biāo)對(duì)象是否實(shí)現(xiàn)接口來(lái)自動(dòng)選擇合適的代理方式。

在實(shí)戰(zhàn)部分,我們一步步搭建 Spring AOP 的開(kāi)發(fā)環(huán)境,通過(guò)注解和 XML 配置兩種方式定義切面、切點(diǎn)以及各種通知類型,如前置通知、后置通知、返回通知、異常通知和環(huán)繞通知,每種通知都有其獨(dú)特的執(zhí)行時(shí)機(jī)和應(yīng)用場(chǎng)景。同時(shí),我們還深入學(xué)習(xí)了切點(diǎn)表達(dá)式的語(yǔ)法和使用,它能精準(zhǔn)地指定哪些方法會(huì)被切面所影響。

進(jìn)一步地,我們探討了 Spring AOP 的高級(jí)特性,如引入功能可以在運(yùn)行時(shí)為目標(biāo)對(duì)象動(dòng)態(tài)添加新的接口和實(shí)現(xiàn);多切面的使用中,我們了解了如何通過(guò)實(shí)現(xiàn)Ordered接口或使用@Order注解來(lái)設(shè)置切面的優(yōu)先級(jí),以控制多個(gè)切面的執(zhí)行順序。最后,我們列舉了 Spring AOP 在日志記錄、事務(wù)管理、權(quán)限控制和性能監(jiān)控等多個(gè)實(shí)際應(yīng)用場(chǎng)景中的具體應(yīng)用。

5.2 知識(shí)擴(kuò)展

  • AspectJ 與 Spring AOP:Spring AOP 雖然功能強(qiáng)大,但它與 AspectJ 有著緊密的聯(lián)系和一些區(qū)別。AspectJ 是一個(gè)功能更為全面的 AOP 框架,它不僅支持運(yùn)行時(shí)織入,還支持編譯時(shí)織入和類加載時(shí)織入。編譯時(shí)織入可以在編譯階段就將切面邏輯織入到字節(jié)碼中,這樣可以獲得更好的性能,因?yàn)椴恍枰谶\(yùn)行時(shí)動(dòng)態(tài)生成代理對(duì)象。類加載時(shí)織入則是在類加載到 JVM 時(shí)進(jìn)行織入。AspectJ 的切點(diǎn)表達(dá)式語(yǔ)言也更為豐富和強(qiáng)大,它可以匹配更多的連接點(diǎn),如字段訪問(wèn)、構(gòu)造函數(shù)調(diào)用等,而 Spring AOP 主要側(cè)重于方法級(jí)別的攔截。在實(shí)際應(yīng)用中,如果對(duì)性能要求極高,或者需要更細(xì)粒度的切面控制,可以考慮使用 AspectJ;而 Spring AOP 則更適合于一般的 Spring 項(xiàng)目,因?yàn)樗c Spring 框架的集成更加緊密,使用起來(lái)更加方便。
  • AOP 在其他框架中的應(yīng)用:除了 Spring 框架,AOP 在其他一些框架中也有廣泛的應(yīng)用。例如,在 Java EE 開(kāi)發(fā)中,EJB(Enterprise JavaBeans)框架就使用了 AOP 的思想來(lái)實(shí)現(xiàn)事務(wù)管理、安全控制等功能。在 Web 開(kāi)發(fā)中,Struts2 框架也可以通過(guò)插件的方式引入 AOP,實(shí)現(xiàn)對(duì) Action 的增強(qiáng),比如日志記錄、權(quán)限檢查等。在一些移動(dòng)開(kāi)發(fā)框架中,AOP 也被用于實(shí)現(xiàn)一些通用的功能,如性能監(jiān)控、錯(cuò)誤處理等,以提高應(yīng)用的質(zhì)量和可維護(hù)性。了解 AOP 在不同框架中的應(yīng)用,可以幫助我們更好地理解 AOP 的通用性和重要性,以及如何在不同的技術(shù)棧中靈活運(yùn)用 AOP 來(lái)解決實(shí)際問(wèn)題。

5.3 閱讀資料推薦

  • Spring 官方文檔:Spring 官方文檔是學(xué)習(xí) Spring AOP 最權(quán)威的資料,它詳細(xì)介紹了 Spring AOP 的各種功能、配置方式以及使用場(chǎng)景,并且會(huì)隨著 Spring 版本的更新而及時(shí)更新,能夠讓我們了解到 Spring AOP 的最新特性和最佳實(shí)踐??梢栽L問(wèn) Spring 官方網(wǎng)站(https://spring.io/projects/spring-framework)獲取相關(guān)文檔。
  • 《Spring 實(shí)戰(zhàn)》:這本書是學(xué)習(xí) Spring 框架的經(jīng)典書籍,其中有專門的章節(jié)深入講解 Spring AOP 的原理和實(shí)踐,通過(guò)大量的代碼示例和實(shí)際案例,幫助讀者更好地理解和掌握 Spring AOP 的使用方法,對(duì)于想要深入學(xué)習(xí) Spring AOP 的開(kāi)發(fā)者來(lái)說(shuō)是一本不可多得的好書。
  • 相關(guān)技術(shù)博客:在技術(shù)博客平臺(tái)上,有許多技術(shù)專家和開(kāi)發(fā)者分享了他們?cè)谑褂?Spring AOP 過(guò)程中的經(jīng)驗(yàn)和心得,如 InfoQ、開(kāi)源中國(guó)等。通過(guò)閱讀這些博客,可以了解到 Spring AOP 在實(shí)際項(xiàng)目中的應(yīng)用案例、遇到的問(wèn)題及解決方案,拓寬我們的技術(shù)視野,學(xué)習(xí)到一些實(shí)用的技巧和方法。

5.4 問(wèn)題探討與互動(dòng)

在使用 Spring AOP 的過(guò)程中,大家是否遇到過(guò)性能瓶頸的問(wèn)題呢?例如,當(dāng)切面數(shù)量較多或者切點(diǎn)表達(dá)式過(guò)于復(fù)雜時(shí),可能會(huì)影響系統(tǒng)的性能。那么如何優(yōu)化 AOP 的性能,減少對(duì)系統(tǒng)性能的影響呢?另外,在多切面的場(chǎng)景下,如何更好地處理切面之間的沖突和依賴關(guān)系,確保切面的執(zhí)行順序符合預(yù)期呢?歡迎大家在評(píng)論區(qū)留言討論,分享自己的經(jīng)驗(yàn)和見(jiàn)解。

到此這篇關(guān)于深入探索Spring AOP:從原理到實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Spring AOP原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 常見(jiàn)的排序算法,一篇就夠了

    常見(jiàn)的排序算法,一篇就夠了

    這篇文章主要介紹了一些常用排序算法整理,插入排序算法、直接插入排序、希爾排序、選擇排序、冒泡排序等排序,需要的朋友可以參考下
    2021-07-07
  • Springboot中登錄后關(guān)于cookie和session攔截問(wèn)題的案例分析

    Springboot中登錄后關(guān)于cookie和session攔截問(wèn)題的案例分析

    這篇文章主要介紹了Springboot中登錄后關(guān)于cookie和session攔截案例,本文通過(guò)實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-08-08
  • JAVA區(qū)間值判斷[10,20)的實(shí)現(xiàn)

    JAVA區(qū)間值判斷[10,20)的實(shí)現(xiàn)

    本文主要介紹了JAVA區(qū)間值判斷[10,20)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-09-09
  • Spring Boot整合Spring Security的示例代碼

    Spring Boot整合Spring Security的示例代碼

    這篇文章主要介紹了Spring Boot整合Spring Security的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • java &與&&的區(qū)別及實(shí)例

    java &與&&的區(qū)別及實(shí)例

    這篇文章主要介紹了java &與&&的區(qū)別的相關(guān)資料,并附簡(jiǎn)單實(shí)例,幫助大家學(xué)習(xí)理解這部分知識(shí),需要的朋友可以參考下
    2016-10-10
  • java開(kāi)發(fā)技巧代碼寫的快且bug少的原因分析

    java開(kāi)發(fā)技巧代碼寫的快且bug少的原因分析

    這篇文章主要為大家介紹了java開(kāi)發(fā)中代碼寫的快且bug少的原因分析及技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Spring Boot項(xiàng)目中Maven編譯參數(shù)source、target與release的區(qū)別及配置實(shí)踐方案

    Spring Boot項(xiàng)目中Maven編譯參數(shù)source、target與release的區(qū)別及配置實(shí)踐方案

    在Spring Boot項(xiàng)目開(kāi)發(fā)中,將本地代碼打包部署到服務(wù)器時(shí),常會(huì)遇到UnsupportedClassVersionError或NoSuchMethodError等異常,本文將直觀解析這些配置的區(qū)別,并提供標(biāo)準(zhǔn)的配置方案,感興趣的朋友跟隨小編一起看看吧
    2026-02-02
  • Spring的@Conditional詳解

    Spring的@Conditional詳解

    這篇文章主要介紹了Spring的@Conditional詳解,給想要注入Bean增加限制條件,只有滿足限制條件才會(huì)被構(gòu)造并注入到Spring的IOC容器中,通常和@Bean注解一起使用,需要的朋友可以參考下
    2024-01-01
  • xxl-job如何濫用netty導(dǎo)致的問(wèn)題及解決方案

    xxl-job如何濫用netty導(dǎo)致的問(wèn)題及解決方案

    本篇文章講解xxl-job作為一款分布式任務(wù)調(diào)度系統(tǒng)是如何濫用netty的,導(dǎo)致了怎樣的后果以及如何修改源碼解決這些問(wèn)題,netty作為一種高性能的網(wǎng)絡(luò)編程框架,十分受大家喜愛(ài),今天就xxl-job濫用netty這一問(wèn)題給大家詳細(xì)下,感興趣的朋友一起看看吧
    2021-05-05
  • Java Object定義三個(gè)點(diǎn)實(shí)現(xiàn)代碼

    Java Object定義三個(gè)點(diǎn)實(shí)現(xiàn)代碼

    這篇文章主要介紹了Java Object定義三個(gè)點(diǎn)實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評(píng)論

卓资县| 齐河县| 当涂县| 乌拉特后旗| 石城县| 远安县| 林周县| 姜堰市| 会理县| 海南省| 丹东市| 长沙市| 三门县| 牙克石市| 孝感市| 丹棱县| 东莞市| 霸州市| 洛扎县| 罗田县| 高雄县| 铜鼓县| 双城市| 庄河市| 博白县| 航空| 鄂州市| 衡南县| 阳高县| 浦县| 色达县| 介休市| 乌兰察布市| 吴桥县| 珠海市| 丰宁| 锡林浩特市| 德惠市| 平遥县| 青海省| 鄯善县|