拼多多二面:Spring AOP 和 AspectJ 的區(qū)別是什么?
Spring AOP和 AspectJ 是工作中經(jīng)常使用的兩個(gè)的 AOP框架,那么,它們是如何工作的??jī)烧咧g有什么區(qū)別?我們?cè)撊绾芜x擇?這篇文章來(lái)聊一聊。
一、實(shí)現(xiàn)原理
1. Spring AOP 的實(shí)現(xiàn)原理
Spring AOP,全稱 Aspect-Oriented Programming,中文翻譯為面向切面編程,主要是基于代理模式來(lái)實(shí)現(xiàn)面向切面編程。其核心原理包括 3個(gè)步驟:
- 動(dòng)態(tài)代理:Spring AOP 使用 JDK 動(dòng)態(tài)代理或 CGLIB(Code Generation Library)生成代理對(duì)象。當(dāng)被代理的目標(biāo)對(duì)象實(shí)現(xiàn)了接口時(shí),Spring 默認(rèn)使用 JDK 動(dòng)態(tài)代理;否則,使用 CGLIB 生成子類代理。
- 織入機(jī)制:在運(yùn)行時(shí),通過(guò)代理對(duì)象攔截方法調(diào)用,根據(jù)配置的切面(Aspect)和通知(Advice)執(zhí)行相應(yīng)的增強(qiáng)邏輯(如前置、后置、環(huán)繞等)。
- 代理鏈:如果有多個(gè)切面,需要按照一定的順序?qū)δ繕?biāo)對(duì)象進(jìn)行多層代理。
2. AspectJ 的實(shí)現(xiàn)原理
AspectJ 是一個(gè)功能更強(qiáng)大的 AOP 框架,提供了更豐富的切面功能和更靈活的織入機(jī)制。其實(shí)現(xiàn)原理也包括 3個(gè)步驟:
織入時(shí)機(jī):
- 編譯時(shí)織入(Compile-time Weaving):在源代碼編譯成字節(jié)碼時(shí),將切面邏輯織入目標(biāo)類。
- 類加載時(shí)織入(Load-time Weaving):在類被加載到 JVM 時(shí),通過(guò)特定的類加載器將切面邏輯織入目標(biāo)類。
- 二進(jìn)制織入(Binary Weaving):對(duì)已經(jīng)編譯好的字節(jié)碼進(jìn)行后期修改,加入切面邏輯。
- 字節(jié)碼操作:AspectJ 直接操作字節(jié)碼,允許對(duì)更細(xì)粒度的連接點(diǎn)(如字段賦值、構(gòu)造方法調(diào)用等)進(jìn)行攔截和增強(qiáng)。
- 豐富的切點(diǎn)表達(dá)式:支持更復(fù)雜和精確的切點(diǎn)定義,涵蓋更多的連接點(diǎn)類型。
二、兩者區(qū)別
在分析完 Spring AOP和 AspectJ 的工作原理之后,我們來(lái)看看兩者的區(qū)別。關(guān)于 Spring AOP和 AspectJ的區(qū)別,可以總結(jié)成下表:
特性 | Spring AOP | AspectJ |
實(shí)現(xiàn)方式 | 基于動(dòng)態(tài)代理(JDK代理或CGLIB) | 基于字節(jié)碼織入(編譯時(shí)、類加載時(shí)、二進(jìn)制) |
切點(diǎn)范圍 | 主要面向方法級(jí)別的連接點(diǎn) | 支持方法、構(gòu)造方法、字段、異常等多種連接點(diǎn) |
織入時(shí)機(jī) | 運(yùn)行時(shí)通過(guò)代理實(shí)現(xiàn) | 編譯時(shí)、類加載時(shí)或二進(jìn)制后期織入 |
性能 | 由于使用代理,性能開銷相對(duì)較小,但功能有限 | 由于織入在編譯或類加載時(shí)完成,運(yùn)行時(shí)性能更優(yōu),功能更強(qiáng)大 |
功能豐富度 | 提供基本的AOP功能,如前置、后置、環(huán)繞通知 | 提供更豐富的AOP功能,包括更復(fù)雜的切點(diǎn)表達(dá)式和連接點(diǎn)類型 |
使用復(fù)雜度 | 易于集成和使用,特別是在Spring應(yīng)用中 | 相對(duì)復(fù)雜,需要了解更多的織入機(jī)制和配置 |
適用場(chǎng)景 | 適合大多數(shù)常見的AOP需求,如事務(wù)管理、日志記錄等 | 適合需要更深入和復(fù)雜AOP功能的場(chǎng)景,如底層框架開發(fā)、對(duì)非Spring管理對(duì)象進(jìn)行增強(qiáng)等 |
三、代碼示例
為了更好地理解 Spring AOP 和 AspectJ,下面我們以如何進(jìn)行日志記錄為例,展示兩者的實(shí)現(xiàn)。
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) 定義業(yè)務(wù)類:
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自動(dòng)代理
publicclass AppConfig {
@Bean
public UserService userService() {
returnnew UserService();
}
@Bean
public LoggingAspect loggingAspect() {
returnnew LoggingAspect();
}
}
(5) 運(yùn)行測(cè)試:
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) 定義業(yè)務(wù)類:
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) 編譯時(shí)織入(使用 AspectJ 編譯器 ajc):
- 假設(shè)項(xiàng)目結(jié)構(gòu)如下:
src/
├── com/yuanjava/service/UserService.java
├── com/yuanjava/aspect/LoggingAspect.java
- 運(yùn)行以下命令進(jìn)行編譯和織入:
ajc -1.8 -d bin -sourcepath src src/com/yuanjava/service/UserService.java src/com/yuanjava/aspect/LoggingAspect.java
(5) 運(yùn)行測(cè)試:
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
四、使用場(chǎng)景
1. Spring AOP
從整體來(lái)看,Spring AOP的使用場(chǎng)景可以包含以下幾個(gè)方面:
- 事務(wù)管理:在業(yè)務(wù)方法執(zhí)行前后開啟和提交事務(wù)。
- 日志記錄:記錄方法執(zhí)行的日志,如進(jìn)入、退出、異常等。
- 權(quán)限檢查:在方法調(diào)用前進(jìn)行權(quán)限驗(yàn)證。
- 緩存管理:在方法執(zhí)行前后進(jìn)行緩存的查詢與更新。
- 性能監(jiān)控:監(jiān)控方法的執(zhí)行時(shí)間,進(jìn)行性能分析。
2. AspectJ
從整體來(lái)看,AspectJ的使用場(chǎng)景可以包含以下幾個(gè)方面:
- 底層框架開發(fā):對(duì)第三方庫(kù)或應(yīng)用程序進(jìn)行更深入的字節(jié)碼增強(qiáng)。
- 跨庫(kù)的數(shù)據(jù)訪問(wèn):在數(shù)據(jù)訪問(wèn)層進(jìn)行統(tǒng)一的攔截和處理。
- 復(fù)雜的業(yè)務(wù)邏輯攔截:需要對(duì)構(gòu)造方法、字段賦值等進(jìn)行攔截的場(chǎng)景。
- 無(wú)Spring環(huán)境的項(xiàng)目:在不使用 Spring 框架的項(xiàng)目中實(shí)現(xiàn) AOP 功能。
- 性能優(yōu)化:需要高性能的字節(jié)碼級(jí)別的增強(qiáng),減少運(yùn)行時(shí)開銷。
- 適用理由:AspectJ 提供更強(qiáng)大的 AOP 功能和更靈活的織入機(jī)制,適用于需要精細(xì)控制切面織入時(shí)機(jī)和范圍的復(fù)雜應(yīng)用。
五、總結(jié)
本文,我們分析了 Spring AOP 和 AspectJ 的實(shí)現(xiàn)原理,并且通過(guò)示例展示了兩者如如何使用它們。
- Spring AOP:Spring AOP 集成簡(jiǎn)單,適用于大多數(shù)基于 Spring 的應(yīng)用,易于集成和配置,適合實(shí)現(xiàn)常見的橫切關(guān)注點(diǎn),如事務(wù)管理和日志記錄。
- AspectJ:AspectJ 提供更強(qiáng)大的 AOP 功能和更靈活的織入機(jī)制,適用于需要深入字節(jié)碼級(jí)別操作或在非 Spring 環(huán)境中實(shí)現(xiàn) AOP 的場(chǎng)景。
在實(shí)際業(yè)務(wù)中,選擇哪種 AOP框架取決于項(xiàng)目的具體需求和團(tuán)隊(duì)的技術(shù)棧選擇,作為 Java程序員,強(qiáng)烈建議掌握兩者的工作原理。