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

從靜態代理到 Spring AOP:Java 代理模式演進與實戰全解析

     編輯:Java 綜合教學

為何需要代理模式?

若直接在業務類別(如 Math)每個方法中硬編碼日誌、計時等橫切邏輯,會違反:

  • 開閉原則 (OCP):修改既有代碼而非擴展。
  • 單一職責 (SRP):業務邏輯與系統服務混雜。
  • 依賴倒轉 (DIP):高層模組依賴具體實現。

代理模式演進路線

1. 靜態代理

  • 手工為每個目標類別寫一個代理類別,實作相同介面。
  • 解決 OCP、DIP,但類別爆炸,維護成本高。

2. JDK 動態代理 (java.lang.reflect.Proxy)

  • 單一 InvocationHandler 處理所有介面方法。
  • 限制:目標類別必須實作至少一個介面。
public class DynamicProxy implements InvocationHandler {
    private Object target;
    public Object getProxy(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this);
    }
    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = m.invoke(target, args);
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
        return result;
    }
}

3. CGLIB 動態代理

  • 繼承目標類別、覆寫方法,無需介面。
  • 限制:無法代理 final 類別/方法。
public class CglibProxy implements MethodInterceptor {
    public Object getProxy(Object target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }
    public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = proxy.invokeSuper(obj, args); // 注意這裡用 invokeSuper
        System.out.println("耗時:" + (System.currentTimeMillis() - start));
        return result;
    }
}

Spring AOP 整合

程式碼式配置 (ProxyFactory)

ProxyFactory factory = new ProxyFactory();
factory.setTarget(new Math());
factory.addAdvice(new TimeSpanAdvice()); // 環繞通知
IMath proxy = (IMath) factory.getProxy();

XML 配置 (ProxyFactoryBean)

<bean id="target" class="com.demo.Math"/>
<bean id="advice" class="com.demo.TimeSpanAdvice"/>
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"
      p:target-ref="target" p:interceptorNames="advice"
      p:proxyTargetClass="true"/> <!-- true 強制 CGLIB -->

Schema-based AOP (AspectJ 風格,推薦)

<aop:config proxy-target-class="true">
  <aop:aspect ref="advice">
    <aop:pointcut id="pc" expression="execution(* com.demo.Math.*(..))"/>
    <aop:before method="before" pointcut-ref="pc"/>
    <aop:after method="after" pointcut-ref="pc"/>
    <aop:around method="around" pointcut-ref="pc"/>
    <aop:after-returning method="afterReturn" pointcut-ref="pc" returning="ret"/>
    <aop:after-throwing method="afterThrow" pointcut-ref="pc" throwing="ex"/>
  </aop:aspect>
</aop:config>

五種通知類型對照

通知介面/註解典型用途
前置MethodBeforeAdvice / @Before權限檢查、日誌
後置AfterReturningAdvice / @AfterReturning結果封裝、資源清理
環繞MethodInterceptor / @Around事務管理、計時、快取
拋出ThrowsAdvice / @AfterThrowing異常統一處理
返回- / @AfterReturning(returning=..)後處理回傳值

核心選型建議

  • 目標類別有介面 → 優先 JDK 動態代理(輕量、標準)。
  • 目標類別無介面或需代理非介面方法 → CGLIB(proxy-target-class="true")。
  • 企業級專案 → 直接使用 Spring AOP + AspectJ 註解/XML,統一管理切面,享受 IOC 容器生命週期。

掌握代理演進脈絡,能在面對效能、相容性、維護性取捨時做出正確架構決策。

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