我們知道軟件開發一直在追求高效,易維護,易擴展的特性方式。在面向過程編程到面向對象編程的歷程中,程序的開發有了非常大的進步。但是oop的方式缺依然存在著一些缺點。oop的方式可以將業務進行很好的分解和封裝使其模塊化,但是卻沒辦法更好的避免類似于系統需求的視線在系統中各處散落這樣的問題。比如:不同的主業務可能都會需要一些與業務無關的邏輯代碼--日志記錄,事務處理等。那么這些相同的無關邏輯在不同業務中的對應關系則會變成1:n的方式。這樣就會造成后續維護的成本賊高。因此推出了AOP的方式來對oop的缺點進行優化。
AOP被稱為面向方面編程或者切面編程。相當于一個刀直接在業務邏輯上橫切。只需要實現AOP的邏輯即可插入到任何的業務邏輯中去從而只需要維護AOP邏輯即可管理所有的非業務邏輯。AOP和OOP是互補的方式而不是競爭的方式。
Java平臺上的AOP實現機制
而在java平臺上實現AOP的方式有以下幾種
動態代理:
JDK1.3之后引入了動態代理的機制,可以在運行期間,為相應的接口動態生成代理對象。所以,我們可以將橫切關注點邏輯封裝到動態代理的InvocationHandler當中,然后在運行期間通過動態代理生成對應對象的子類將橫切邏輯織入到代理對象當中。這種方式相比于靜態代理則是性能稍微遜色一些。同時有一個缺點就是只能實現接口的動態代理,那些沒有接口實現的類則無法實現動態代理
動態字節碼增強:
java虛擬機加載的class文件都是符合一定的規范的,所以只要交給java虛擬機運行的class文件符合java class規范那么程序運行就沒有什么問題。而通常情況下class文件是通過java編譯期編譯而形成的。而java則推出了CGLIB的方式等java工具庫,在運行期間通過修改字節碼的方式來實現動態增強,將新增的業務邏輯加入到AOP邏輯當中。CJLIB的方式則不需要java類去繼承接口,只需要java類能夠生成子類即可就能實現動態字節碼增強
AOP的各個組件
Joinpoint:
在系統運行之前,aop的功能就需要織入到OOP當中,所以這個注入的過程我們需要知道在OOP代碼中哪些執行點上進行織入操作,這些將要在其之上進行織入操作的系統執行點就是Joinpoint
比如上述的代碼執行流程圖當中我們可以在以下階段進行織入:
方法調用:當某個方法被調用的時候所處的程序執行點,圖中的三個圓圈則是屬于這種類型。
方法調用執行:這種方式的本質是在執行,也就是在方法體內執行的時候才會調用。與方法調用的區別在于一般方法調用先執行而方法調用執行后執行
構造方法調用:與方法調用意思幾乎相同,在構造方法進行初始化的時候進行執行
構造方法調用執行:則是在執行構造方法的時候才會真正的執行
字段設置:在設置某個屬性通過setter方法被設置或者直接被設置的時點。該Joinpoint的本質是對象屬性被設置時候觸發
字段獲取:在獲取相對字段的時候也就是通過getter方法訪問或者直接訪問的時候則會觸發
異常處理執行:當出現異常的時候,則會在對應的異常拋出點執行
類初始化:類初始化則是類中某些靜態類型或者靜態代碼塊初始化的時間點上的時候會執行
基本上在代碼執行的過程中所有的執行時點上都可以作為Joinpoint,那么這些joinpoint有了如果我們僅僅在需要的地方執行如何告訴程序呢?
Poincut
Poincut則是告訴程序需要在哪些Joinpoint上去執行AOP邏輯處理,如果說Joinpoint是所有的可執行點,那么Poincut則是真正需要執行AOP的執行點。
在 Spring AOP 中,Pointcut 主要通過以下方式實現:
1.?基于注解的 Pointcut
使用?@Pointcut
?注解定義切入點表達式,常見的表達式類型包括:
- execution:匹配方法執行連接點。
- within:匹配指定類型內的方法。
- @annotation:匹配帶有特定注解的方法。
2.?execution 表達式
最常用的表達式類型,語法為:
execution(修飾符? 返回類型 類路徑?方法名(參數) throws 異常?)
2.?其他表達式類型
within:限制匹配范圍為特定類型。
// 匹配 UserService 類中的所有方法
within(com.example.service.UserService)// 匹配 service 包下的所有類的方法
within(com.example.service.*)// 匹配 service 包及其子包下的所有類的方法
within(com.example.service..*)
@annotation:匹配帶有特定注解的方法。
// 匹配所有帶 @Loggable 注解的方法
@annotation(com.example.annotation.Loggable)
2.?基于 XML 配置的 Pointcut
在 Spring XML 配置中,可以使用?<aop:pointcut>
?元素定義切入點
Pointcut 本身只是定義篩選條件,需要與通知(Advice)?結合才能實現 AOP 的增強功能
💡?黃金法則:
Pointcut = 在哪里切(Where) + 切什么(What)
好的切點表達式應該像精準的手術刀,而非大錘。
Adivce
Advice(通知)?是核心概念之一,用于定義在目標方法執行的特定時機插入的橫切邏輯(如日志、事務、權限校驗等)。它決定了切面(Aspect)行為的具體執行位置和內容。
以下是Spring AOP支持的Advice類型及其執行時機:
通知類型 | 注解 | 執行時機 |
---|---|---|
Before Advice | @Before | 在目標方法執行前觸發(例如:權限校驗) |
After Returning | @AfterReturning | 在目標方法成功返回后觸發(例如:記錄操作結果) |
After Throwing | @AfterThrowing | 在目標方法拋出異常后觸發(例如:異常日志記錄) |
After (Finally) | @After | 在目標方法結束后觸發(無論成功/異常,類似finally 塊,例如:資源清理) |
Around Advice | @Around | 環繞目標方法執行,可控制方法調用、修改參數/返回值(例如:性能監控、事務管理) |
Advice 是 Spring AOP 實現橫切邏輯的核心機制,通過五種類型覆蓋方法執行的各個生命周期節點,顯著提升代碼復用性和可維護性。正確使用Advice能高效解決日志、事務、安全等橫切關注點問題,是Spring生態中不可或缺的組件。
織入和織入器
織入?是指將?切面(Aspect)?中定義的橫切邏輯(Advice)動態植入到目標對象(Target Object)?的過程。類比織布,就是將切面的"線"編織到業務邏輯的"布料"中。
關鍵作用:
動態增強目標對象
在目標方法執行的特定位置(如方法調用前、返回后、異常時)插入橫切邏輯(如日志、事務等)。實現邏輯解耦
將核心業務邏輯(如用戶服務)與非功能性需求(如日志記錄)分離,通過織入機制在運行時組合。支持多種植入方式
織入時機 | 實現方式 | 特點 |
---|---|---|
編譯時織入 | 使用 AspectJ 編譯器 | 性能高,但需要特殊編譯步驟(如 Maven 插件) |
類加載時織入 | 通過 Java Agent(LTW) | 無需修改源碼,在 JVM 加載類時植入(Spring 支持) |
運行時織入 | Spring AOP 默認方式(動態代理) | 無需編譯支持,但僅作用于 Spring 容器管理的 Bean(最常用) |
織入器?是負責執行織入操作的底層引擎。在 Spring AOP 中,織入器由框架內部實現,開發者通過配置驅動其工作。
核心職責:
解析切面配置
讀取?@Aspect
?注解或 XML 配置,識別 Pointcut(切入點)和 Advice(通知)的定義。匹配連接點(Join Point)
根據 Pointcut 表達式(如?execution(* com.service.*.*(..))
)定位需要增強的目標方法。生成代理對象
代理類型 觸發條件 實現方式 JDK 動態代理 目標類實現了接口 基于? java.lang.reflect.Proxy
CGLIB 代理 目標類未實現接口(或強制使用) 通過字節碼生成子類(如? UserService$$EnhancerBySpringCGLIB
)植入 Advice 邏輯
在代理對象中按順序集成各類通知(如?@Before
、@Around
),構建執行鏈。