溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么解決Spring?AOP攔截抽象類父類中方法失效問題

發(fā)布時間:2021-11-24 13:35:00 來源:億速云 閱讀:265 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹怎么解決Spring AOP攔截抽象類父類中方法失效問題,文中介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們一定要看完!

背景

最近工作中需要對組內(nèi)各個系統(tǒng)依賴的第三方接口進行監(jiān)控報警,對于下游出現(xiàn)問題的接口能夠及時感知.首先我們寫了一個Spring AOP注解,用于收集調(diào)用第三方時返回的信息.而我們調(diào)用第三方的類抽象出一個父類.并在父類的方法中加入我們的自定義注解用于監(jiān)控日志并打印日志.

怎么解決Spring?AOP攔截抽象類父類中方法失效問題

很多子類繼承了這個父類并使用父類中的方法.如:

怎么解決Spring?AOP攔截抽象類父類中方法失效問題

當(dāng)調(diào)用子類的doSomething方法時問題出現(xiàn)了,發(fā)現(xiàn)Spring AOP沒有攔截doPost()方法.而將注解加在子類方法上時,Spring AOP可以攔截子類的方法,但這不是我們想要的結(jié)果.而當(dāng)我們將父類通過@Autowired方式注入到子類中代替使用繼承的方式調(diào)用父類中方法時Spring AOP可以攔截父類中的方法.至此發(fā)現(xiàn)問題出現(xiàn)在繼承上面.

原因分析

Spring AOP攔截器的實現(xiàn)原理就是利用動態(tài)代理技術(shù)實現(xiàn)面向切面編程,Spring 的代理實現(xiàn)有兩種:一是基于 JDK Dynamic Proxy 技術(shù)而實現(xiàn)的;二是基于 CGLIB 技術(shù)而實現(xiàn)的。如果目標(biāo)對象實現(xiàn)了接口,在默認(rèn)情況下Spring會采用JDK的動態(tài)代理實現(xiàn)AOP,在本例目標(biāo)對象沒有實現(xiàn)接口,因此使用的CGLIB實現(xiàn)動態(tài)代理對SuperClass對象進行代理,然后增強doPost()方法.下面的代碼展示了為什么Spring AOP沒有增強doPost()方法.

怎么解決Spring?AOP攔截抽象類父類中方法失效問題

圖2等價于圖3,即使用super關(guān)鍵字調(diào)用doPost()方法,這就表明我們使用的SuperClass的實例調(diào)用的doPost()方法,在我們在使用Spring AOP的時候,我們從IOC容器中獲取的Bean對象其實都是代理對象,而不是那些Bean對象本身.因此AOP不能使用代理對象調(diào)用這些父類的方法.

解決方案

知道了問題原因,解決問題就比較容易了,由于我們使用的super關(guān)鍵字調(diào)用父類的方法行不通,那么我們就強制使用代理對象調(diào)用父類方法.

怎么解決Spring?AOP攔截抽象類父類中方法失效問題

好了,我們運行程序,發(fā)現(xiàn)不但沒有攔截方法而且還報錯了.

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

異常信息非常明確,找不到當(dāng)前的代理,需要在暴露出代理,我們看下AopContext這個類的源碼,看看到底哪里出錯了,看到了我們輸出錯誤信息的地方.

package org.springframework.aop.framework;
import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.Nullable;
public final class AopContext {
    private static final ThreadLocal<Object> currentProxy = new NamedThreadLocal("Current AOP proxy");
    private AopContext() {
    }
    public static Object currentProxy() throws IllegalStateException {
        Object proxy = currentProxy.get();
        if (proxy == null) {
            throw new IllegalStateException("Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.");
        } else {
            return proxy;
        }
    }
    @Nullable
    static Object setCurrentProxy(@Nullable Object proxy) {
        Object old = currentProxy.get();
        if (proxy != null) {
            currentProxy.set(proxy);
        } else {
            currentProxy.remove();
        }
        return old;
    }
}

說名setCurrentProxy方法沒有被調(diào)用,通過查找發(fā)現(xiàn)有兩個類調(diào)用了該方法,分別為CglibAopProxyJdkDynamicAopProxy,是不是很熟悉,這兩個就是Spring aop的代理方式,由于我們討論的目標(biāo)對象不是基于接口的,因此本文使用的代理都是基于CglibAopProxy,我們找到該類中調(diào)用setCurrentProxy方法的地方,程序中判斷this.advised.exposeProxy是否為true,如果為true,設(shè)置當(dāng)前代理,而通過調(diào)試這個字段為false

if (this.advised.exposeProxy) {
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }

那么我們就在需要通知的地方,即你需要攔截方法的類上加上如下注解.

@EnableAspectJAutoProxy(exposeProxy = true)

這次在調(diào)用,發(fā)現(xiàn)已經(jīng)可以攔截注解標(biāo)注的方法了.

后記

解決這個問題的方式有很多,可以子類不繼承父類,而是改為@Autowired方式,然后每一個調(diào)用的地方需要加上父類的對象.

最終我們在程序中的方案是加了一個父類的代理類,用于強制使用代理對象調(diào)用父類的方法.而原來父類的子類繼承代理類即可.

import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
public class ProxyAgent extends BaseAgent{
    private BaseAgent getProxyObject() {
        return ((BaseAgent)AopContext.currentProxy());
    }
    protected String doGet(String url, Map<String, Object> headers, Map<String, Object> params, Object... uriVariables) {
        return getProxyObject().doGetBase(url, headers, params, uriVariables);
    }
    protected String doPost(String url, Map<String, Object> headers, Object body) {
        return getProxyObject().doPostBase(url, headers, body);
    }
    protected String doPostForm(String url, Map<String, Object> params) {
        return doPostForm(url, null, params);
    }
    protected String doPostForm(String url, Map<String, Object> headers, Map<String, Object> params) {
        return getProxyObject().doPostFormBase(url, headers, params);
    }
    protected String doPostFormWithContentHeader(String url, Map<String, Object> headers,
                                                 Map<String, Object> params, byte[] boundary) {
        return getProxyObject().doPostFormWithContentHeaderBase(url, headers, params, boundary);
    }
    protected String doPostFormUpload(String url, Map<String, Object> headers, Map<String, Object> params) {
        return getProxyObject().doPostFormUploadBase(url, headers, params);
    }
}

同理,調(diào)用內(nèi)部方法使用this關(guān)鍵字時同樣會出現(xiàn)這個問題,同樣采用強制使用代理對象即可.

以上是“怎么解決Spring AOP攔截抽象類父類中方法失效問題”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI