解決Spring?AOP攔截抽象類(父類)中方法失效問題
背景
最近工作中需要對(duì)組內(nèi)各個(gè)系統(tǒng)依賴的第三方接口進(jìn)行監(jiān)控報(bào)警,對(duì)于下游出現(xiàn)問題的接口能夠及時(shí)感知.首先我們寫了一個(gè)Spring AOP注解,用于收集調(diào)用第三方時(shí)返回的信息.而我們調(diào)用第三方的類抽象出一個(gè)父類.并在父類的方法中加入我們的自定義注解用于監(jiān)控日志并打印日志.
很多子類繼承了這個(gè)父類并使用父類中的方法.如:
當(dāng)調(diào)用子類的doSomething方法時(shí)問題出現(xiàn)了,發(fā)現(xiàn)Spring AOP沒有攔截doPost()方法.而將注解加在子類方法上時(shí),Spring AOP可以攔截子類的方法,但這不是我們想要的結(jié)果.而當(dāng)我們將父類通過@Autowired方式注入到子類中代替使用繼承的方式調(diào)用父類中方法時(shí)Spring AOP可以攔截父類中的方法.至此發(fā)現(xiàn)問題出現(xiàn)在繼承上面.
原因分析
Spring AOP攔截器的實(shí)現(xiàn)原理就是利用動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)面向切面編程,Spring 的代理實(shí)現(xiàn)有兩種:一是基于 JDK Dynamic Proxy 技術(shù)而實(shí)現(xiàn)的;二是基于 CGLIB 技術(shù)而實(shí)現(xiàn)的。如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,在默認(rèn)情況下Spring會(huì)采用JDK的動(dòng)態(tài)代理實(shí)現(xiàn)AOP,在本例目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,因此使用的CGLIB實(shí)現(xiàn)動(dòng)態(tài)代理對(duì)SuperClass對(duì)象進(jìn)行代理,然后增強(qiáng)doPost()方法.下面的代碼展示了為什么Spring AOP沒有增強(qiáng)doPost()方法.
圖2等價(jià)于圖3,即使用super關(guān)鍵字調(diào)用doPost()方法,這就表明我們使用的SuperClass的實(shí)例調(diào)用的doPost()方法,在我們在使用Spring AOP的時(shí)候,我們從IOC容器中獲取的Bean對(duì)象其實(shí)都是代理對(duì)象,而不是那些Bean對(duì)象本身.因此AOP不能使用代理對(duì)象調(diào)用這些父類的方法.
解決方案
知道了問題原因,解決問題就比較容易了,由于我們使用的super關(guān)鍵字調(diào)用父類的方法行不通,那么我們就強(qiáng)制使用代理對(duì)象調(diào)用父類方法.
好了,我們運(yùn)行程序,發(fā)現(xiàn)不但沒有攔截方法而且還報(bào)錯(cuò)了.
java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
異常信息非常明確,找不到當(dāng)前的代理,需要在暴露出代理,我們看下AopContext這個(gè)類的源碼,看看到底哪里出錯(cuò)了,看到了我們輸出錯(cuò)誤信息的地方.
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)有兩個(gè)類調(diào)用了該方法,分別為CglibAopProxy和JdkDynamicAopProxy,是不是很熟悉,這兩個(gè)就是Spring aop的代理方式,由于我們討論的目標(biāo)對(duì)象不是基于接口的,因此本文使用的代理都是基于CglibAopProxy,我們找到該類中調(diào)用setCurrentProxy方法的地方,程序中判斷this.advised.exposeProxy是否為true,如果為true,設(shè)置當(dāng)前代理,而通過調(diào)試這個(gè)字段為false
if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; }
那么我們就在需要通知的地方,即你需要攔截方法的類上加上如下注解.
@EnableAspectJAutoProxy(exposeProxy = true)
這次在調(diào)用,發(fā)現(xiàn)已經(jīng)可以攔截注解標(biāo)注的方法了.
后記
解決這個(gè)問題的方式有很多,可以子類不繼承父類,而是改為@Autowired方式,然后每一個(gè)調(diào)用的地方需要加上父類的對(duì)象.
最終我們在程序中的方案是加了一個(gè)父類的代理類,用于強(qiáng)制使用代理對(duì)象調(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)鍵字時(shí)同樣會(huì)出現(xiàn)這個(gè)問題,同樣采用強(qiáng)制使用代理對(duì)象即可.
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SSM?Mapper文件查詢出返回?cái)?shù)據(jù)查不到個(gè)別字段的問題
這篇文章主要介紹了SSM?Mapper文件查詢出返回?cái)?shù)據(jù)查不到個(gè)別字段的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Springboot集成百度地圖實(shí)現(xiàn)定位打卡的示例代碼
本文主要介紹了Springboot集成百度地圖實(shí)現(xiàn)定位打卡的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02解決@Transaction注解導(dǎo)致動(dòng)態(tài)切換更改數(shù)據(jù)庫失效問題
這篇文章主要介紹了解決@Transaction注解導(dǎo)致動(dòng)態(tài)切換更改數(shù)據(jù)庫失效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09詳解使用Spring的BeanPostProcessor優(yōu)雅的實(shí)現(xiàn)工廠模式
這篇文章主要介紹了詳解使用Spring的BeanPostProcessor優(yōu)雅的實(shí)現(xiàn)工廠模式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程
這篇文章主要介紹了spring security集成cas實(shí)現(xiàn)單點(diǎn)登錄過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java concurrency之AtomicLong原子類_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
AtomicLong是作用是對(duì)長整形進(jìn)行原子操作。下面通過本文給大家介紹Java concurrency之AtomicLong原子類的相關(guān)知識(shí),感興趣的朋友一起看看吧2017-06-06