面向切面編程(AOP)是 Spring 框架的核心功能之一,它允許開發者將橫切關注點(如日誌、事務、安全)與業務邏輯分離。Spring AOP 基於代理模式,支援五種通知類型:@Before(前置)、@After(後置)、@Around(環繞)、@AfterReturning(返回後)、@AfterThrowing(拋出異常後)。
<!-- Spring AOP + AspectJ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.21</version> <!-- 建議升級版本 -->
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.21</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version> <!-- 建議升級版本 -->
</dependency>
<aop:aspectj-autoproxy expose-proxy="true"/>
在 applicationContext.xml 中加入上述配置,即可啟用基於註解的 AOP 功能。expose-proxy="true" 允許通過 AopContext.currentProxy() 獲取當前代理對象。
@Component
@Aspect
public class CustomAspect {
// 1. execution 表達式:攔截 services 包及子包下所有以 for 開頭的方法
@Pointcut("execution(* com.maven.project.services..for*(..))")
public void query() {}
// 2. 註解驅動切點:攜帶 @QuerySystem 註解的方法
@Pointcut("@annotation(com.maven.project.web.customAnnotations.QuerySystem)")
public void loginTimeOut() {}
// 前置通知
@Before("query()")
public void queryBefore(JoinPoint joinPoint) {
System.out.println("=======query start @Before ====方法名稱:" + joinPoint.getSignature().getName());
}
// 後置通知
@After("query()")
public void queryAfter(JoinPoint joinPoint) {
System.out.println("=======query end @After ====方法名稱:" + joinPoint.getSignature().getName());
}
// 環繞通知:需手動調用 proceed() 並返回結果
@Around("query()")
public Object queryAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("=======query start @Around ====方法名稱:" + joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); // 必須返回結果
System.out.println("=======query end @Around ====方法名稱:" + joinPoint.getSignature().getName());
return result;
}
// 統計方法執行時間
@Around("loginTimeOut()")
public Object times(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("======= 登陸耗時:" + (end - start) + "ms ====方法名稱:" + joinPoint.getSignature().getName());
return result;
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface QuerySystem {
String description() default "";
}
@RequestMapping("/login")
@QuerySystem(description = "用戶登錄接口")
public void login(HttpServletRequest request, HttpServletResponse response) {
// 業務邏輯
}
execution(modifiers? return-type declaring-type? method-name(param) throws?)
* 表示任意類型com.maven.project.services.. 表示該包及所有子包* 表示所有類for* 表示以 for 開頭的方法(..) 表示任意參數| 表達式 | 匹配範圍 |
|---|---|
execution(public * *(..)) | 所有 public 方法 |
execution(* *To(..)) | 所有以 To 結尾的方法 |
execution(* com.baobaotao.Waiter.*(..)) | Waiter 接口所有方法 |
execution(* com.baobaotao..*(..)) | com.baobaotao 包及子包所有類的所有方法 |
execution(* joke(String,..)) | 第一個參數為 String 的 joke 方法 |
@Annotation 切點,業務語義清晰,重構安全joinPoint.proceed() 結果,否則目標方法返回值丟失expose-proxy 以提升性能