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

AOP面向切面編程四大實現方式對比:從經典代理到AspectJ註解與XML配置

     編輯:Java 綜合教學

Spring AOP 四大實現方式總覽

方式核心機制配置形式適用場景學習難度
1. 經典 ProxyFactoryBean手工裝配代理工廠、顧問、通知XML Bean 定義教學理解原理、極簡項目、舊版本維護★★★☆☆
2. 自動代理創建器BeanPostProcessor 掃描 Advisor 自動代理XML 宣告 AutoProxyCreatorSpring 內部機制、批量代理固定模式★★☆☆☆
3. @AspectJ 註解註解驅動,AspectJ 切點表達式Java 註解 + @EnableAspectJAutoProxy現代主流標準、功能最全、重構友好★★☆☆☆
4. Schema XML (aop:config)命名空間配置,純 POJO 切面XML aop:config/aop:aspect無法修改源碼第三方類、配置外部化需求★★☆☆☆

方式一:經典 ProxyFactoryBean 完整實戰

接口與實現

public interface Advice {
    void firstMethod();
    String secondMethod();
}

public class AdviceImpl implements Advice {
    public void firstMethod() { System.out.println("==firstMethod=="); }
    public String secondMethod() { System.out.println("==secondMethod=="); return "abc"; }
}

四類通知實現

// 前置通知
public class MethodBeforeAdvice implements org.springframework.aop.MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) {
        System.out.println("==Before==");
    }
}

// 後置通知
public class AfterReturningAdvice implements org.springframework.aop.AfterReturningAdvice {
    public void afterReturning(Object ret, Method method, Object[] args, Object target) {
        System.out.println("==After==");
    }
}

// 環繞通知 (最強大,實現 MethodInterceptor)
public class MyInterceptor implements org.aopalliance.intercept.MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("==Around Before==");
        Object result = invocation.proceed(); // 執行目標方法
        System.out.println("==Around After==");
        return result;
    }
}

// 異常通知
public class MyException implements org.springframework.aop.ThrowsAdvice {
    public void afterThrowing(Exception ex) {
        System.out.println("異常通知: " + ex.getMessage());
    }
}

XML 裝配 (含顧問包裝通知)

<beans>
    <bean id="adviceImpl" class="com.example.AdviceImpl"/>
    
    <bean id="beforeAdvice" class="com.example.MethodBeforeAdvice"/>
    <bean id="afterAdvice" class="com.example.AfterReturningAdvice"/>
    <bean id="aroundAdvice" class="com.example.MyInterceptor"/>
    <bean id="throwAdvice" class="com.example.MyException"/>
    
    <!-- 顧問:方法名匹配 -->
    <bean id="nameMatchAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="beforeAdvice"/>
        <property name="mappedNames" value="firstMethod,secondMethod"/>
    </bean>
    
    <!-- 顧問:正則匹配 -->
    <bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="aroundAdvice"/>
        <property name="pattern" value=".*Method.*"/>
    </bean>
    
    <!-- 自動代理:BeanNameAutoProxyCreator -->
    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="beanNames" value="adviceImpl"/>
        <property name="interceptorNames" value="nameMatchAdvisor,regexAdvisor"/>
    </bean>
</beans>

方式二:最原始經典 AOP (分層架構演示)

DAO → Biz → Service(AOP) → Test

// DAO
public interface UDao { void save(User user); }
public class UserDaoImpl implements UDao { public void save(User u) { System.out.println("保存成功"); } }

// Biz
public interface UBiz { void save2(User user); }
public class UserBiz implements UBiz {
    private UDao udao;
    public void save2(User u) { udao.save(u); }
    // setter/getter
}

// Service (通知)
public class LoggerBefore implements MethodBeforeAdvice {
    public void before(Method m, Object[] args, Object t) { System.out.println("save前置"); }
}
public class LoggerAfter implements AfterReturningAdvice {
    public void afterReturning(Object r, Method m, Object[] args, Object t) { System.out.println("save後置"); }
}

// XML 配置 (aop:config 簡化版)
<aop:config>
    <aop:pointcut expression="execution(public void save2(..))" id="pc"/>
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    <aop:advisor advice-ref="after" pointcut-ref="pc"/>
</aop:config>

方式三:@AspectJ 註解 (推薦標準)

@Aspect
@Component
public class Aspect {
    @Before("execution(public * *(..))")
    public void asBefore() { System.out.println("前置增強"); }
    
    @AfterReturning("execution(public * *(..))")
    public void asAfterReturning() { System.out.println("後置增強"); }
    
    @Around("execution(public * *(..))")
    public void asAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("環繞前");
        pjp.proceed();
        System.out.println("環繞後");
    }
    
    @AfterThrowing("execution(public * *(..))")
    public void asThrowing() { System.out.println("異常增強"); }
    
    @After("execution(public * *(..))")
    public void asAfter() { System.out.println("最終增強"); }
}
<beans>
    <bean id="aspectImpl" class="com.example.AspectjImpl"/>
    <bean id="aspect" class="com.example.Aspect"/>
    <aop:aspectj-autoproxy /> <!-- 關鍵:啟用註解掃描 -->
</beans>

方式四:AspectJ XML 配置 (純 POJO 切面)

// 切面類:移除所有註解,保留普通方法
public class Aspect {
    public void asBefore() { System.out.println("前置增強"); }
    public void asAfterReturning() { System.out.println("後置增強"); }
    public void asAround(ProceedingJoinPoint pjp) throws Throwable { /*...*/ }
    public void asThrowing() { System.out.println("異常增強"); }
    public void asAfter() { System.out.println("最終增強"); }
}
<aop:config>
    <aop:pointcut expression="execution(public * *(..))" id="pc"/>
    <aop:aspect ref="aspect">
        <aop:before method="asBefore" pointcut-ref="pc"/>
        <aop:after-returning method="asAfterReturning" pointcut-ref="pc"/>
        <!-- 環繞需傳 ProceedingJoinPoint 參數 -->
        <aop:around method="asAround" pointcut-ref="pc"/>
        <aop:after-throwing method="asThrowing" pointcut-ref="pc"/>
        <aop:after method="asAfter" pointcut-ref="pc"/>
    </aop:aspect>
</aop:config>

五大通知類型執行順序與異常流轉

// 正常流程
@Before → @Around (proceed前) → 目標方法 → @Around (proceed後) → @AfterReturning → @After

// 異常流程
@Before → @Around (proceed前) → 目標方法拋異常 → @Around (catch塊/拋出) → @AfterThrowing → @After
// 注意:@AfterReturning 不執行,@After 最終增強始終執行 (類似 finally)

代理機制:JDK vs CGLIB 選擇策略

  • 目標實現接口:默認 JDK 動態代理 (實現接口),proxy-target-class=true 強制 CGLIB
  • 目標未實現接口:強制 CGLIB (繼承子類)
  • final 方法:CGLIB 無法代理,JDK 代理接口不受影響
  • 性能:CGLIB 創建慢 (字節碼生成)、調用快 (FastClass);JDK 創建快、調用慢 (反射),單例場景 CGLIB 更優

總結:如何選擇?

  1. 新項目/可修改源碼:@AspectJ 註解 — 代碼即文檔、IDE導航、重構安全、功能完整
  2. 第三方庫/不可修改類:aop:config XML — 外部化配置、POJO切面、無侵入
  3. 理解原理/教學/極簡場景:ProxyFactoryBean — 手工裝配見微知著
  4. 批量固定模式代理:AutoProxyCreator — 內部機制,極少直接使用

無論哪種方式,本質都是 BeanPostProcessor + 動態代理 (JDK/CGLIB) 在 Bean 初始化後攔截替換。掌握切點表達式、通知類型、執行順序、代理機制四大核心,即可駕馭 Spring AOP 解決所有橫切關注點問題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved