拼多多二面:Spring AOP 和 AspectJ 的區別是什么?
Spring AOP和 AspectJ 是工作中經常使用的兩個的 AOP框架,那么,它們是如何工作的?兩者之間有什么區別?我們該如何選擇?這篇文章來聊一聊。
一、實現原理
1. Spring AOP 的實現原理
Spring AOP,全稱 Aspect-Oriented Programming,中文翻譯為面向切面編程,主要是基于代理模式來實現面向切面編程。其核心原理包括 3個步驟:
- 動態代理:Spring AOP 使用 JDK 動態代理或 CGLIB(Code Generation Library)生成代理對象。當被代理的目標對象實現了接口時,Spring 默認使用 JDK 動態代理;否則,使用 CGLIB 生成子類代理。
- 織入機制:在運行時,通過代理對象攔截方法調用,根據配置的切面(Aspect)和通知(Advice)執行相應的增強邏輯(如前置、后置、環繞等)。
- 代理鏈:如果有多個切面,需要按照一定的順序對目標對象進行多層代理。
2. AspectJ 的實現原理
AspectJ 是一個功能更強大的 AOP 框架,提供了更豐富的切面功能和更靈活的織入機制。其實現原理也包括 3個步驟:
織入時機:
- 編譯時織入(Compile-time Weaving):在源代碼編譯成字節碼時,將切面邏輯織入目標類。
- 類加載時織入(Load-time Weaving):在類被加載到 JVM 時,通過特定的類加載器將切面邏輯織入目標類。
- 二進制織入(Binary Weaving):對已經編譯好的字節碼進行后期修改,加入切面邏輯。
- 字節碼操作:AspectJ 直接操作字節碼,允許對更細粒度的連接點(如字段賦值、構造方法調用等)進行攔截和增強。
- 豐富的切點表達式:支持更復雜和精確的切點定義,涵蓋更多的連接點類型。
二、兩者區別
在分析完 Spring AOP和 AspectJ 的工作原理之后,我們來看看兩者的區別。關于 Spring AOP和 AspectJ的區別,可以總結成下表:
特性 | Spring AOP | AspectJ |
實現方式 | 基于動態代理(JDK代理或CGLIB) | 基于字節碼織入(編譯時、類加載時、二進制) |
切點范圍 | 主要面向方法級別的連接點 | 支持方法、構造方法、字段、異常等多種連接點 |
織入時機 | 運行時通過代理實現 | 編譯時、類加載時或二進制后期織入 |
性能 | 由于使用代理,性能開銷相對較小,但功能有限 | 由于織入在編譯或類加載時完成,運行時性能更優,功能更強大 |
功能豐富度 | 提供基本的AOP功能,如前置、后置、環繞通知 | 提供更豐富的AOP功能,包括更復雜的切點表達式和連接點類型 |
使用復雜度 | 易于集成和使用,特別是在Spring應用中 | 相對復雜,需要了解更多的織入機制和配置 |
適用場景 | 適合大多數常見的AOP需求,如事務管理、日志記錄等 | 適合需要更深入和復雜AOP功能的場景,如底層框架開發、對非Spring管理對象進行增強等 |
三、代碼示例
為了更好地理解 Spring AOP 和 AspectJ,下面我們以如何進行日志記錄為例,展示兩者的實現。
1. 使用 Spring AOP
步驟:
(1) 添加依賴(以 Maven 為例):
<dependencies>
<!-- 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.19</version>
</dependency>
</dependencies>
(2) 定義業務類:
package com.yuanjava.service;
public class UserService {
public void addUser(String name) {
System.out.println("Add user: " + name);
}
}
(3) 定義切面類:
package com.yuanjava.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.yuanjava.service.UserService.addUser(..))")
public void logBefore() {
System.out.println("Add log before method");
}
}
(4) 配置 Spring 容器(使用 Java 配置):
package com.yuanjava.config;
import com.example.aspect.LoggingAspect;
import com.example.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy// 啟用AOP自動代理
publicclass AppConfig {
@Bean
public UserService userService() {
returnnew UserService();
}
@Bean
public LoggingAspect loggingAspect() {
returnnew LoggingAspect();
}
}
(5) 運行測試:
package com.yuanjava;
import com.yuanjava.config.AppConfig;
import com.yuanjava.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AOPSpringDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
userService.addUser("猿java");
context.close();
}
}
輸出:
Add log before method
Add user: 猿java
2. 使用 AspectJ
步驟:
(1) 添加依賴(以 Maven 為例):
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.19</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.19</version>
</dependency>
</dependencies>
(2) 定義業務類:
package com.yuanjava.service;
public class UserService {
public void addUser(String name) {
System.out.println("Add user: " + name);
}
}
(3) 定義切面類:
package com.yuanjava.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.yuanjava.service.UserService.addUser(..))")
public void logBefore() {
System.out.println("Add log before method");
}
}
(4) 編譯時織入(使用 AspectJ 編譯器 ajc):
- 假設項目結構如下:
src/
├── com/yuanjava/service/UserService.java
├── com/yuanjava/aspect/LoggingAspect.java
- 運行以下命令進行編譯和織入:
ajc -1.8 -d bin -sourcepath src src/com/yuanjava/service/UserService.java src/com/yuanjava/aspect/LoggingAspect.java
(5) 運行測試:
package com.yuanjava;
import com.yuanjava.service.UserService;
public class AspectJDemos {
public static void main(String[] args) {
UserService userService = new UserService();
userService.addUser("猿java");
}
}
輸出:
Add log before method
Add user: 猿java
四、使用場景
1. Spring AOP
從整體來看,Spring AOP的使用場景可以包含以下幾個方面:
- 事務管理:在業務方法執行前后開啟和提交事務。
- 日志記錄:記錄方法執行的日志,如進入、退出、異常等。
- 權限檢查:在方法調用前進行權限驗證。
- 緩存管理:在方法執行前后進行緩存的查詢與更新。
- 性能監控:監控方法的執行時間,進行性能分析。
2. AspectJ
從整體來看,AspectJ的使用場景可以包含以下幾個方面:
- 底層框架開發:對第三方庫或應用程序進行更深入的字節碼增強。
- 跨庫的數據訪問:在數據訪問層進行統一的攔截和處理。
- 復雜的業務邏輯攔截:需要對構造方法、字段賦值等進行攔截的場景。
- 無Spring環境的項目:在不使用 Spring 框架的項目中實現 AOP 功能。
- 性能優化:需要高性能的字節碼級別的增強,減少運行時開銷。
- 適用理由:AspectJ 提供更強大的 AOP 功能和更靈活的織入機制,適用于需要精細控制切面織入時機和范圍的復雜應用。
五、總結
本文,我們分析了 Spring AOP 和 AspectJ 的實現原理,并且通過示例展示了兩者如如何使用它們。
- Spring AOP:Spring AOP 集成簡單,適用于大多數基于 Spring 的應用,易于集成和配置,適合實現常見的橫切關注點,如事務管理和日志記錄。
- AspectJ:AspectJ 提供更強大的 AOP 功能和更靈活的織入機制,適用于需要深入字節碼級別操作或在非 Spring 環境中實現 AOP 的場景。
在實際業務中,選擇哪種 AOP框架取決于項目的具體需求和團隊的技術棧選擇,作為 Java程序員,強烈建議掌握兩者的工作原理。