首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring AOP 從淺入深完整指南:核心概念、五種實現策略、代理模式、切點表達式、AspectJ 整合、XML/註解雙軌配置

     編輯:Java 綜合教學

Spring AOP 從淺入深完整指南

面向切面程式設計 (AOP) 將橫切關注點 (事務、日誌、安全、快取) 模組化,降低業務代碼耦合。本文系統講解 AOP 核心術語、五大實現策略、Spring 代理機制 (JDK/CGLib)、編程式/聲明式增強、切點表達式、AspectJ 整合、自動代理、XML 與註解雙軌配置,附完整代碼範例。

一、核心術語 (AOP 詞彙表)

術語定義對應 Spring
Join Point (連接點)程式執行中可插入切面的點 (方法呼叫、異常拋出、欄位修改)Spring 僅支援方法執行連接點
Pointcut (切點)匹配一組連接點的謂語 (正則/表達式)execution(* com..service.*.*(..))
Advice (通知/增強)在切點執行的動作 (前/後/環繞/拋出/引入)@Before, @AfterReturning, @Around, @AfterThrowing, @DeclareParents
Aspect (切面)切點 + 通知 的模組化封裝@Aspect 類
Introduction (引入增強)為現有類動態添加方法/欄位/介面實現@DeclareParents, DelegatingIntroductionInterceptor
Weaving (織入)將切面應用到目標對象的過程運行期動態代理 (Spring)、編譯期 (AspectJ)、類載入期 (JBoss AOP)
Target (目標對象)被代理的業務對象Spring Bean
Proxy (代理)織入增強後的對象JDK 動態代理 / CGLib 子類代理

二、AOP 五大實現策略對比

策略原理代表框架優點缺點
JDK 動態代理實現介面生成匿名類,反射調用Spring AOP (預設)標準 API、無第三方依賴僅能代理介面、反射開銷
CGLib 字節碼生成繼承目標類生成子類,覆寫方法Spring AOP (proxyTargetClass=true)可代理類、效能優於反射無法代理 final 類/方法、構造子雙調用
自定義類載入器類載入時修改字節碼JBoss AOP可攔截構造子、欄位、靜態方法複雜、需特殊啟動參數
編譯期織入編譯階段修改 .classAspectJ (ajc 編譯器)效能最好、支援所有連接點需專用編譯器、構建流程複雜
語言擴展擴展 Java 語法 (關鍵字 aspect)AspectJ (.aj 檔案)語法豐富、可攔截構造子/欄位非標準 Java、IDE 支援有限

三、Spring AOP 代理機制核心規則

  • 目標類實現介面 → **預設 JDK 動態代理** (可強制 CGLib: proxy-target-class="true")。
  • 目標類未實現介面 → **強制 CGLib** (生成子類)。
  • @Transactional 等註解驅動的 AOP 同樣遵循此規則。
  • 代理物件 只能轉型為介面 (JDK) 或類 (CGLib),不可混用。

四、編程式 vs 聲明式增強實戰

1. 編程式 (ProxyFactoryBean / ProxyFactory)

// 目標介面/類
public interface Greeting { void sayHello(String name); }
public class GreetingImpl implements Greeting {
    public void sayHello(String name) { System.out.println("Hello, " + name); }
}

// 通知 (MethodInterceptor = 環繞通知)
public class LoggingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("[Before] " + invocation.getMethod().getName());
        Object ret = invocation.proceed(); // 執行目標方法
        System.out.println("[After] " + invocation.getMethod().getName());
        return ret;
    }
}

// XML 配置
<bean id="greetingImpl" class="GreetingImpl"/>
<bean id="loggingInterceptor" class="LoggingInterceptor"/>
<bean id="greetingProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="greetingImpl"/>
    <property name="interceptorNames">
        <list><value>loggingInterceptor</value></list>
    </property>
</bean>

// 測試
Greeting proxy = (Greeting) ctx.getBean("greetingProxy");
proxy.sayHello("Spring"); // 輸出 Before/After 包裹

2. 聲明式 (Schema-based / @AspectJ)

// Schema-based XML (aop 命名空間)
<aop:config>
    <aop:aspect ref="loggingAspect">
        <aop:pointcut id="serviceMethods" expression="execution(* com..service.*.*(..))"/>
        <aop:before pointcut-ref="serviceMethods" method="logBefore"/>
        <aop:after-returning pointcut-ref="serviceMethods" method="logAfter"/>
        <aop:around pointcut-ref="serviceMethods" method="logAround"/>
    </aop:aspect>
</aop:config>

@Component("loggingAspect")
public class LoggingAspect {
    public void logBefore(JoinPoint jp) { System.out.println("[Before] " + jp.getSignature()); }
    public void logAfter(JoinPoint jp) { System.out.println("[After] " + jp.getSignature()); }
    public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("[Around Before]");
        Object ret = pjp.proceed();
        System.out.println("[Around After]");
        return ret;
    }
}

五、AspectJ 切點表達式語法速查

表達式匹配目標
execution(public * *(..))所有 public 方法
execution(* com.app.service.*.*(..))service 包下所有類的所有方法
execution(* com.app.service.UserService.*(..))UserService 所有方法
execution(* com.app.service.UserService.save*(..))save 開頭方法
execution(* *(..)) && @annotation(Transactional)帶 @Transactional 註解的方法
within(com.app.service.*)service 包下所有類 (含非 public)
this(com.app.service.UserService)代理物件實現 UserService 介面
target(com.app.service.UserServiceImpl)目標物件是 UserServiceImpl
args(String, ..)第一參數為 String 的方法
@within(Secured)類帶 @Secured 註解
@annotation(Secured)方法帶 @Secured 註解

六、引入增強 (Introduction) 實戰

Spring AOP 方式 (DelegatingIntroductionInterceptor)

// 新介面
public interface Lockable { void lock(); void unlock(); boolean locked(); }

// 實現
public class LockableImpl implements Lockable {
    private boolean locked;
    public void lock() { locked = true; }
    public void unlock() { locked = false; }
    public boolean locked() { return locked; }
}

// XML 配置 (需 proxy-target-class="true" 使用 CGLib 代理類)
<bean id="lockableAdvisor" class="org.springframework.aop.support.DelegatingIntroductionInterceptor">
    <constructor-arg><bean class="LockableImpl"/></constructor-arg>
</bean>
<bean id="target" class="com.app.UserServiceImpl"/>
<bean class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="target"/>
    <property name="interceptorNames"><list><value>lockableAdvisor</value></list></property>
    <property name="proxyTargetClass" value="true"/> <!-- 關鍵:代理類而非介面 -->
</bean>

// 使用
UserServiceImpl proxy = (UserServiceImpl) ctx.getBean("target");
Lockable lockable = (Lockable) proxy; // 強轉新介面
lockable.lock(); // 動態實現

AspectJ 方式 (@DeclareParents) - 更強大,支援介面/類切點

@Aspect
public class LockableAspect {
    @DeclareParents(value = "com.app.service.*+", defaultImpl = LockableImpl.class)
    public static Lockable lockable; // 靜態欄位持有介面引用
}

// 配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean class="LockableAspect"/>

// 使用 (可面向介面編程)
UserService proxy = ctx.getBean("userService", UserService.class);
Lockable lockable = (Lockable) proxy; // 直接轉型
lockable.lock();

七、自動代理機制

類別配置特點
BeanNameAutoProxyCreatorbeanNames="*Service"按 Bean 名稱後綴匹配
DefaultAdvisorAutoProxyCreator<aop:config> 隱式註冊掃描所有 Advisor (Pointcut+Advice) 自動代理匹配 Bean
AnnotationAwareAspectJAutoProxyCreator<aop:aspectj-autoproxy/>掃描 @Aspect Bean,最常用、功能最全

八、XML 純配置風格 (無註解侵入)

<aop:config proxy-target-class="true">
    <aop:aspect id="txAspect" ref="txAdvice">
        <aop:pointcut id="serviceMethods" expression="execution(* com.app.service.*.*(..))"/>
        <aop:around pointcut-ref="serviceMethods" method="invoke"/>
    </aop:aspect>
</aop:config>

<bean id="txAdvice" class="com.app.TxAdvice"/> <!-- 實現 MethodInterceptor -->

九、Spring AOP vs AspectJ 選型建議

  • Spring AOP:運行期代理、僅方法攔截、零侵入、啟動快、適合 95% 企業場景 (事務、日誌、權限、快取)。
  • AspectJ:編譯期/類載入期織入、支援構造子/欄位/靜態方法攔截、效能極致、需 ajc 編譯器或 LTW (Load-Time Weaving) 配置、適合基礎設施層、極高效能要求、領域模型驅動設計 (DDD)。
  • 混合使用:Spring 核心業務用 Spring AOP,底層框架/領域模型用 AspectJ (可透過 @EnableAspectJAutoProxy 整合)。

十、常見坑點避雷

  1. 內部方法調用不生效:this.method() 繞過代理 → 注入自己 (@Autowired SelfService self) 或 AopContext.currentProxy()。
  2. final 方法/類無法代理 (CGLib):移除 final 或改用介面 + JDK 代理。
  3. 事務不回滾:檢查異常類型 (需 rollbackFor=Exception.class)、是否吞掉異常、傳播行為、DataSource AutoCommit。
  4. 切點表達式寫錯:execution(* com..service.*.*(..)) 雙點號匹配子包,單點號僅當前包。
  5. 多介面實現類代理轉型失敗:JDK 代理只能轉介面,需 proxy-target-class="true" 轉具體類。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved