Spring 的 Aop 切面編程的主要用途是:在不改變相關方法原有代碼的情況下,實現對相關方法的功能增強,其本質就是采用動態代理技術來實現的。有關 Spring 的 Aop 底層原理所采用的動態代理技術,我將在下篇博客進行介紹。
本篇博客主要介紹 Spring 如何采用純注解的方式,對相關方法進行 Aop 擴展增強。有關 Spring 的 Aop 的相關術語,這里不進行詳細介紹,網上資料很多,限於篇幅有限,這里僅僅介紹如果進行快速搭建和使用,在本篇博客的最后會提供 Demo 的源代碼。
一、搭建工程
新建一個 maven 項目,導入相關 jar 包,我導入的都是最新版本的 jar 包,內容如下:
有關具體的 jar 包地址,可以在 https://mvnrepository.com 上進行查詢。
<dependencies>
<!--Spring 的基礎核心 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.17</version>
</dependency>
<!--第三方 Aop 切面編程 jar 包-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
</dependency>
<!--
由於本 Demo 需要使用 junit 進行單元測試,
因此需要導入 junit 和 spring-test 這兩個 jar 包,
用於 spring 整合 junit 單元測試
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.17</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
打開右側的 Maven 窗口,刷新一下,這樣 Maven 會自動下載所需的 jar 包文件。
搭建好的項目工程整體目錄比較簡單,具體如下圖所示:

項目工程結構簡單介紹:
aop 包下存放 Aop 的方法類,這些方法用於對相關 Service 類中的方法增強
service 包下存放的是業務處理實現類
AopApp 這個是該 demo 的 main 入口所在類,使用 Spring 訪問數據庫,驗證搭建成果
test 目錄下 EmployeeAopTest 這個是測試方法類,里面編寫測試 Service 接口的方法,驗證 Aop 的執行情況
二、Service 業務方法的細節
本 Demo 中只有一個接口 EmployeeService 和一個實現類 EmployeeServiceImpl ,里面的內容非常簡單。
需要注意的是:
Service 的業務方法類最好要實現接口,因為在實際開發場景中,Aop 切面配置的位置,最佳方案是配置在接口上,而不是配置到具體的實現類上。這樣的好處在於后續如果接口有其它的實現類,Aop 切面增強功能也在接口的其它實現類上自動生效。
package com.jobs.service;
public interface EmployeeService {
//不會報錯的方法
Integer normalTest(Integer xxx, Integer yyy);
//會出現異常的方法
void errorTest(Integer zzz);
//測試方法
String aopTest(String ppp, String qqq);
}
package com.jobs.service.impl;
import com.jobs.service.EmployeeService;
import org.springframework.stereotype.Service;
//通過 @Service 注解將該實現類的實例化對象,裝載到 Spring 容器中,方便后續注入使用
@Service("empService")
public class EmployeeServiceImpl implements EmployeeService {
//不會報錯的方法,測試 Aop 方法執行
@Override
public Integer normalTest(Integer xxx, Integer yyy) {
Integer result = xxx + yyy;
System.out.println("normalTest 方法執行了,計算結果為:" + result);
return result;
}
//會出現異常的方法,測試 Aop 方法執行
@Override
public void errorTest(Integer zzz) {
System.out.println("errorTest 方法執行了,必然會拋出異常...");
//這里必然會拋出異常
Integer result = zzz / 0;
}
//測試方法,測試 Aop 方法執行
@Override
public String aopTest(String ppp, String qqq) {
String result = ppp + "-------" + qqq;
System.out.println("aopTest 方法執行了,結果為:" + result);
return result;
}
}
本 Demo 的業務方法,只有 3 個,實現內容也非常簡單。
normalTest 方法用來展示 Aop 在【被增強的方法】正常不報錯情況下的執行順序。
errorTest 方法用於展示 Aop 在【被增強的方法】出錯的情況下的執行順序。
aopTest 方法用於在本 Demo 的啟動類 AopApp 中,驗證本 Demo 的 Aop 功能是否搭建成功。
三、Spring 純注解配置 Aop 細節
編寫 Aop 功能的方法類 AopAdvice ,該類中的方法用於在不改變 Service 中方法的情況下,強行介入到具體方法的執行過程中,實現對 Service 方法的增強。AopAdvice 類的具體內容如下:
package com.jobs.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//使用 @Component 注解將 AopAdvice 的實例化對象,裝載到 Spring 容器中
//使用 @Aspect 注解表示該類是切面類,里面提供了切面增強的方法
@Component
@Aspect
public class AopAdvice {
//使用 @Pointcut 注解,設置要為哪些類的哪些方法進行切面方法增強
//這里設置的是,針對 com.jobs.service 包下的所有 Service 類中的方法進行增強
@Pointcut("execution(* com.jobs.service.*Service.*(..))")
public void pt() {
}
//使用 @Before 注解,表示在【被增強的方法】之前執行下面的代碼
//該注解修飾的方法,可以有一個 JoinPoint 參數,用於獲取【被增強的方法】的相關信息
@Before("pt()")
public void before(JoinPoint jp) {
System.out.println("前置 before 方法執行了...");
//獲取所執行方法的簽名信息
Signature signature = jp.getSignature();
System.out.println("\t 方法簽名為:" + signature);
//通過簽名獲取執行接口名稱或類名
String interfaceName = signature.getDeclaringTypeName();
System.out.println("\t 方法所屬接口或所屬類名為:" + interfaceName);
//通過簽名獲取執行方法名稱
String methodName = signature.getName();
System.out.println("\t 方法名為:" + methodName);
//獲取方法的所傳入的參數值
Object[] args = jp.getArgs();
for (Object obj : args) {
System.out.println("\t 方法傳入的參數值:" + obj);
}
}
//使用 @After 注解,表示在【被增強的方法】之后執行下面的代碼
//無論【被增強的方法】在執行過程中,是否出錯拋出異常,都會執行下面的代碼
//該注解修飾的方法,可以有一個 JoinPoint 參數,用於獲取【被增強的方法】的相關信息
@After("pt()")
public void after(JoinPoint jp) {
//后置方法,通過 jp 參數,也能夠獲取到所執行方法的相關信息
System.out.println("后置 after 方法執行了...");
}
//使用 @AfterReturning 注解,表示在【被增強的方法】返回結果后,執行下面的代碼
//如果【被增強的方法】在執行過程中,是否出錯拋出異常,將不會執行下面的代碼
//該注解修飾的方法,可以有一個 Object 參數,用於獲取【被增強的方法】執行后的返回值
@AfterReturning(pointcut = "pt()", returning = "ret")
public void afterReturing(Object ret) {
//通過參數,可以獲取到方法執行的結果
System.out.println("返回結果后 afterReturing 執行了,返回結果為:" + ret);
}
//使用 @AfterThrowing 注解,表示【被增強的方法】出錯拋異常后,執行下面的代碼
//如果【被增強的方法】沒有出錯拋異常,將不會執行下面的代碼
//該注解修飾的方法,可以有一個 Throwable 參數,用於獲取【被增強的方法】出錯時的異常對象
@AfterThrowing(pointcut = "pt()", throwing = "t")
public void afterThrowing(Throwable t) {
//通過參數,可以獲取到異常對象,從而獲取異常信息
System.out.println("拋出異常后 afterThrowing 執行了,異常信息為:" + t.getMessage());
}
//使用 @Around 注解,可以在【被增強的方法】執行前后,增加相關代碼
// @Around 注解是我們以后用的最多的注解,因為其功能強大靈活。
//該注解修飾的方法,可以有一個 ProceedingJoinPoint 參數,用於獲取【被增強的方法】的相關信息
//可以通過 ProceedingJoinPoint 參數的 proceed() 來執行【被增強的方法】
//需要注意的是:如果這里沒有對【被增強的方法】進行 try catch 包裹處理異常,
//那么【被增強的方法】如果出現異常,將不會執行后面的代碼,這樣后置增強代碼就不會執行了
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞前 around before 執行了...");
//在環繞方法中,可以獲取到方法執行的結果
Object ret = pjp.proceed();
System.out.println("環繞后 around after 執行了...");
return ret;
}
}
下面我們創建一個 Spring 配置類 SpringConfig 並啟用 Aop 功能,具體內容如下:
package com.jobs.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
// @Configuration 注解,表示該類是 Spring 的配置類
// @ComponentScan 注解,表示掃描具體包及其子包下的所有注解,將相關對象實例化並裝載到 Spring 容器中
// @EnableAspectJAutoProxy 這個就是啟用 Aop 功能的注解
@Configuration
@ComponentScan("com.jobs")
@EnableAspectJAutoProxy
public class SpringConfig {
}
下面我們創建一個擁有 main 方法的入口類,調用 EmployeeService 接口中的 aopTest 方法,驗證搭建成果。
package com.jobs;
import com.jobs.config.SpringConfig;
import com.jobs.service.EmployeeService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AopApp {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
EmployeeService empService = (EmployeeService) ctx.getBean("empService");
empService.aopTest("侯胖胖","任肥肥");
}
}
因為 aopTest 方法內部不會出異常,所以不會執行 Aop 的切面類 AopAdvice 下 @AfterThrowing 注解修飾的方法,其它注解的方法都會正常執行,從而實現對 aopTest 的增強。具體如下圖所示:

本 Demo 把 Aop 的所有注解方法都用上了,目的在於演示 Aop 各種注解增強后的方法,具體的執行順序。
從上圖的執行結果可以發現:
@Around 注解修飾的方法(環繞通知),
其內部【被增強方法之前的代碼】在【最前面】執行,【被增強方法之后的代碼】在【最后面】執行。
(上圖中的紅色框輸出的內容)
@Before 注解修飾的方法(前置通知),
其實際執行順序僅次於【環繞通知】的【被增強方法之前的代碼】。
(上圖中的藍色框輸出的內容)
綠色框內輸出的內容,是【被增強方法】執行時輸出的,其實際執行順序僅次於【前置通知】。
(上圖中的綠色框輸出的內容)
@AfterReturning 注解修飾的方法(返回結果通知),其執行順序僅次於【被增強的方法】。
(上圖中的紫色框輸出的內容)
@After 注解修飾的方法(后置通知),在【返回結果通知】之后執行。
(上圖中的黑色框輸出的內容)
四、測試異常方法 Aop 執行順序
前面的博客已經介紹過 Spring 如何整合 junit ,這里就不再詳細介紹了,下面列出編寫的測試類內容:
package com.jobs;
import com.jobs.config.SpringConfig;
import com.jobs.service.EmployeeService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class EmployeeAopTest {
@Autowired
private EmployeeService employeeService;
@Test
public void normalTest() {
int result = employeeService.normalTest(120, 130);
//Assert.assertEquals 主要用來驗證【執行結果】與【所期望的結果】是否一致
Assert.assertEquals(250, result);
}
@Test
public void errorTest() {
try {
//此方法執行會拋出異常,從而導致 AfterThrowing 執行
employeeService.errorTest(38);
} catch (Exception ex) {
System.out.println("單元測試方法獲得的異常信息:" + ex.getMessage());
}
}
@Test
public void aopTest() {
String result = employeeService.aopTest("候肥肥", "任胖胖");
//Assert.assertEquals 主要用來驗證【執行結果】與【所期望的結果】是否一致
Assert.assertEquals("候肥肥-------任胖胖", result);
}
}
有關 normalTest 方法測試的 Aop 執行順序,跟 aopTest 方法的測試結果一樣,因為都是沒有報錯的正常方法。
下面列出 errorTest 方法測試的 Aop 執行順序,employeeService.errorTest 方法會拋異常,其執行結果如下:

從上圖中可以發現,當【被增強的方法】在執行過程中出錯拋出異常,Aop 的執行順序為:
@Around 注解修飾的方法(環繞通知),
其內部【被增強方法之前的代碼】在【最前面】執行,【被增強方法之后的代碼】不會被執行。
(上圖中的紅色框輸出的內容)
@Before 注解修飾的方法(前置通知),
其實際執行順序僅次於【環繞通知】的【被增強方法之前的代碼】。
(上圖中的藍色框輸出的內容)
綠色框內輸出的內容,是【被增強方法】執行時輸出的,其實際執行順序僅次於【前置通知】。
(上圖中的綠色框輸出的內容)
@AfterReturning 注解修飾的方法(返回結果通知),
當【被增強的方法】在執行過程中出錯拋出異常時,不會被執行。
@AfterThrowing 注解修飾的方法(異常通知),
當【被增強的方法】在執行過程中出錯拋出異常時,會被執行。
(上圖中的紫色框輸出的內容)
@After 注解修飾的方法(后置通知),
無論【被增強的方法】在執行過程中是否出錯拋出異常時,都會執行。
(上圖中的黑色框輸出的內容)
上圖中黃色框中的內容,是單元測試方法打印出來的。
到此為止,已經完成了 Spring 有關 Aop 切面編程快速搭建和使用的介紹,大家可以根據實際情況在工作中進行參考使用。限於篇幅有限,這里沒有介紹具體的 Aop 概念和細節。在實際開發場景中,使用最多的是 Aop 的環繞通知,其它 Aop 切面通知很少使用。
最后提供本 Demo 的源代碼:https://files.cnblogs.com/files/blogs/699532/spring_aop_junit.zip
