面向切面編程(AOP)是 Spring 核心特性之一,實現橫切關注點(事務、日誌、安全)與業務邏輯解耦。Spring 支持兩種 AOP 配置風格:@AspectJ 註解風格(推薦,類型安全、IDE 友好)與 Schema XML 風格(傳統、無侵入)。本文從 Java 註解基礎切入,深度講解兩種風格的完整用法與切點表達式。
JDK 5 引入註解,本質是實現 java.lang.annotation.Annotation 接口的特殊接口。關鍵元註解:
@Retention(RetentionPolicy.RUNTIME):運行期保留,反射可讀(AOP 必須)@Target(ElementType.METHOD/TYPE/...):限定註解作用目標@Documented:納入 Javadoc@Inherited:子類繼承父類註解// 1. 定義
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogExecution {
String value() default "";
}
// 2. 使用
@LogExecution("用戶登錄")
public void login() { ... }
// 3. 反射處理
Method m = clazz.getMethod("login");
if (m.isAnnotationPresent(LogExecution.class)) {
LogExecution ann = m.getAnnotation(LogExecution.class);
System.out.println(ann.value());
}
需引入 spring-aop、aspectjweaver 依賴,並啟用 @EnableAspectJAutoProxy。
@Component
public class GreetingService {
public void greetTo(String name) {
System.out.println("Hello, " + name);
}
}
@Aspect
@Component
public class LoggingAspect {
// 前置增強:方法執行前
@Before("execution(* greetTo(..))")
public void beforeGreeting() {
System.out.println("[前置] 即將問候...");
}
// 後置增強:方法正常返回後
@AfterReturning(pointcut = "execution(* greetTo(..))", returning = "result")
public void afterGreeting(Object result) {
System.out.println("[後置] 問候完成");
}
// 環繞增強:完全控制方法執行(最強大,慎用)
@Around("execution(* greetTo(..))")
public Object aroundGreeting(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("[環繞-前] 開始");
Object ret = pjp.proceed(); // 執行目標方法
System.out.println("[環繞-後] 結束");
return ret;
}
// 異常增強:拋出異常時
@AfterThrowing(pointcut = "execution(* greetTo(..))", throwing = "ex")
public void onException(Exception ex) {
System.out.println("[異常] " + ex.getMessage());
}
// 最終增強:finally 語義
@After("execution(* greetTo(..))")
public void finallyGreeting() {
System.out.println("[最終] 清理資源");
}
}
// 程式化織入(測試/獨立應用)
AspectJProxyFactory factory = new AspectJProxyFactory(target);
factory.addAspect(LoggingAspect.class);
GreetingService proxy = factory.getProxy();
proxy.greetTo("Spring");
零侵入業務代碼,適合不想依賴 AspectJ 註解或需動態調整切面的場景。
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:aop="https://www.springframework.org/schema/aop"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="...">
<bean id="greetingService" class="com.example.GreetingService"/>
<bean id="loggingAspect" class="com.example.LoggingAspect"/>
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="greetPointcut" expression="execution(* greetTo(..))"/>
<aop:before method="beforeGreeting" pointcut-ref="greetPointcut"/>
<aop:after-returning method="afterGreeting" pointcut-ref="greetPointcut" returning="result"/>
<aop:around method="aroundGreeting" pointcut-ref="greetPointcut"/>
<aop:after-throwing method="onException" pointcut-ref="greetPointcut" throwing="ex"/>
<aop:after method="finallyGreeting" pointcut-ref="greetPointcut"/>
</aop:aspect>
</aop:config>
</beans>
切點表達式定義「在何處」織入增強,execution 是最常用指示符。
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
| 表達式 | 匹配範例 |
|---|---|
execution(* greetTo(..)) |
任意返回類型、任意參數的 greetTo 方法 |
execution(public * *(..)) |
所有 public 方法 |
execution(* com.service.*.*(..)) |
service 包下所有類的所有方法 |
execution(* *.*(String, ..)) |
第一參數為 String 的方法 |
execution(@Annotation * *(..)) |
帶 @Annotation 註解的方法 |
within(types):指定類型內的方法this(type) / target(type):代理/目標對象類型匹配args(args):參數類型匹配@target(Annotation) / @annotation(Annotation):目標類/方法註解匹配bean(id):Spring Bean 名稱匹配(Spring 專有)&& (and) || (or) ! (not)
execution(* save(..)) && within(com.service..*)
| 增強類型 | 用途 | 關鍵點 |
|---|---|---|
| @Before | 前置檢查、日誌、安全驗證 | 不能阻止方法執行(除非拋異常) |
| @AfterReturning | 結果後處理、緩存更新 | 訪問返回值(returning) |
| @AfterThrowing | 異常日誌、回滾標記 | 訪問異常對象(throwing) |
| @After | 資源清理、監控上報 | 無論正常/異常都執行(finally) |
| @Around | 事務管理、性能監控、緩存攔截 | 需手動 pjp.proceed(),性能影響最大 |
execution(* *(..)) 這類全匹配,影響啟動性能與可讀性。@EnableAspectJAutoProxy(exposeProxy = true),配合 AopContext.currentProxy() 實現代理內部方法調用攔截。Spring AOP 通過動態代理(JDK/CGLIB)在運行期織入切面,@AspectJ 註解與Schema XML提供同等表達能力。掌握切點表達式、增強類型語義、織入機制,能讓你優雅地實現事務、日誌、安全、監控等橫切關注點,保持業務代碼純粹專注。
