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

如何使用 Java 動態代理實現 AOP 攔截功能:從痛點到完整框架雛形

     編輯:Java 相關知識

業務背景:SOAP 服務端的重複代碼地獄

專案架構:SOAP → JNI → C++ 業務邏輯。SOAP 端每個方法僅做三件事:記錄入口日誌、初始化 JavaBean 參數、呼叫 JNI、記錄出口日誌。方法數十個,代碼靠複製貼上維護,新增橫切邏輯(如效能統計、事務邊界)需修改所有方法,極度脆弱。

解決方案:動態代理實現 AOP 攔截器鏈

1. 定義攔截器契約

public interface Interceptor {
    void before(InvokeJniInfo info);        // 方法前
    void after(InvokeJniInfo info);         // 方法後(正常返回)
    void exceptionThrow(InvokeJniInfo info); // 方法拋出異常
}

2. 統一上下文物件


public class InvokeJniInfo {
    private Object proxy;           // 代理物件
    private Method method;          // 被呼叫方法
    private Object[] args;          // 參數陣列
    private Object result;          // 返回值
    private Throwable exception;    // 異常物件

    public InvokeJniInfo(Object proxy, Method method, Object[] args,
                         Object result, Throwable exception) {
        this.proxy = proxy; this.method = method; this.args = args;
        this.result = result; this.exception = exception;
    }
    // Getter/Setter 略
}

3. 抽象基類:減少樣板代碼

public abstract class AbstractInterceptor implements Interceptor {
    public void before(InvokeJniInfo info) {}
    public void after(InvokeJniInfo info) {}
    public void exceptionThrow(InvokeJniInfo info) {}
}

4. 具體攔截器實作

// 日誌攔截器
public class LogInterceptor implements Interceptor {
    private Log log = LogFactory.getLog("aop.log");
    public void before(InvokeJniInfo info) {
        log.debug("Enter " + info.getMethod().getName());
    }
    public void after(InvokeJniInfo info) {
        log.debug("Exit " + info.getMethod().getName());
    }
    public void exceptionThrow(InvokeJniInfo info) {
        log.error("Error in " + info.getMethod().getName(), info.getException());
    }
}

// 參數初始化攔截器
public class InitParamsInterceptor extends AbstractInterceptor {
    public void before(InvokeJniInfo info) {
        if (info.getArgs().length > 0) {
            InitContainsObjectNullUtil.initContainsOutParameter(info.getArgs()[0]);
        }
    }
}

5. 核心:InvocationHandler 實現攔截器鏈調度


public class InterceptorHandler implements InvocationHandler {
    private static final Log log = LogFactory.getLog(InterceptorHandler.class);
    private final List interceptors = new ArrayList<>();
    private final Object target;

    private InterceptorHandler(Object target) {
        this.target = target;
        // 註冊預設攔截器,順序即執行順序
        interceptors.add(new LogInterceptor());
        interceptors.add(new InitParamsInterceptor());
    }

    // 工廠方法
    public static Object newProxyInstance(Object target) {
        return Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new InterceptorHandler(target)
        );
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        InvokeJniInfo info = new InvokeJniInfo(proxy, method, args, null, null);
        Object result = null;
        Throwable thrown = null;

        // 1. Before 鏈(正序)
        log.debug("Invoking Before Interceptors");
        for (Interceptor ic : interceptors) ic.before(info);

        try {
            // 2. 真實方法呼叫
            log.debug("Invoking Target Method");
            result = method.invoke(target, args);
            info.setResult(result);

            // 3. After 鏈(逆序,模擬棧展開)
            log.debug("Invoking After Interceptors");
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                interceptors.get(i).after(info);
            }
        } catch (Throwable t) {
            thrown = t;
            info.setException(t);
            // 4. Exception 鏈(逆序)
            log.debug("Invoking Exception Interceptors");
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                interceptors.get(i).exceptionThrow(info);
            }
            throw t; // 保持異常傳播
        }
        return result;
    }
}

6. 工廠類:簡化客戶端使用


public class InterceptorFactory {
    private static final Log log = LogFactory.getLog(InterceptorFactory.class);

    public static Object getInterceptorProxyedObject(String className) {
        try {
            Class clazz = Class.forName(className);
            Object target = clazz.newInstance();
            return InterceptorHandler.newProxyInstance(target);
        } catch (Exception e) {
            log.error("Failed to create proxy for " + className, e);
            return null;
        }
    }
}

重構前後對比

重構前:每個方法手動編織

public class SOAP {
    private JniInterface jni = new JniClass();
    public JavaBeanA aMethod(JavaBeanA param) {
        log.debug("進入A方法");
        InitContainsObjectNullUtil.initContainsOutParameter(param);
        JavaBeanA result = jni.aMethod(param);
        log.debug("退出A方法");
        return result;
    }
    // 數十個類似方法...
}

重構後:業務邏輯純淨,攔截器自動織入

public class SOAP {
    private JniInterface jni;
    public SOAP() {
        // 關鍵:獲得被代理的 JNI 介面實例
        this.jni = (JniInterface) InterceptorFactory.getInterceptorProxyedObject("JniClass");
    }
    public JavaBeanA aMethod(JavaBeanA param) {
        return jni.aMethod(param); // 一行搞定
    }
    // 其他方法同理,零橫切代碼
}

架構啟示:Spring AOP 的核心原理

  1. 面向介面編程:JDK 動態代理要求目標實作介面,Spring 強制 Bean 定義介面正是此故。
  2. BeanFactory/ApplicationContext:負責建立代理而非原始物件,getBean() 返回代理,實現攔截器自動裝配。
  3. Advisor/Pointcut/Advice:對應本文的 Interceptor、切點匹配邏輯(此處簡化為全方法攔截)、before/after/throws 通知類型。
  4. ProxyFactoryBean:對應 InterceptorFactory,整合切點解析、通知排序、代理生成策略(JDK/CGLIB)。

局限與擴充方向

  • 僅支援介面代理 → 引入 CGLIB/ByteBuddy 實現類別代理。
  • 無切點表達式 → 整合 AspectJ Pointcut Parser,支援 execution(* com..service.*(..)) 等模式。
  • 攔截器順序硬編碼 → 引入 @Order 或優先級配置。
  • 無事務管理整合 → 實作 TransactionInterceptor 結合 PlatformTransactionManager。

理解此精簡實現,即掌握 Spring AOP 核心運作機制,亦具備自行構建輕量 AOP 框架的能力。

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