Aop踩坑!記一次模板類呼叫注入屬性為空的問題
問題起因
在做一個需求的時候,發現原來的程式碼邏輯都是基於模板+泛型的設計模式,模板用於規整邏輯處理流程,泛型用來轉換引數和選取實現類。聽上去是不是很nice!
-
類目錄結構
-
AbstractTestAop:頂層抽象類,定義骨架和執行順序,內部通過Autowired注入了TopClassBean的例項物件。
- AbstractTestCglibAop:二級抽象類,繼承自AbstractTestAop,空類無實現。
- TestCglibAopExample:具體子類,類上添加了@Component註解,空類無實現。
- TestAopRemoteEntrance:呼叫入口,它是一個Bean。
- TopClassBean:例項物件,內部提供一個方法用來表示被呼叫。
- AsyncExportLogAspect:方法切面( 路徑可以自己配置,此處對切面路徑做了處理所以飄紅 )
單元測試

單測結果:

很明顯:頂層介面內部例項引用的TopClassBean物件未注入,屬性為空,導致空指標!
排查
方法debug
-
獲取bean
可以看到此時獲取到的Bean型別為一個代理類,繼續往下,進入到invoke方法
2. before()

可以發現進入到 protected
修飾的 Before
方法的時候由代理轉變為實際的類方法呼叫了
-
myDo()
進入到 final
修飾的 Mydo
方法的時候又由實際類切換到代理類呼叫了,這時候內部引用 topClassBean
為空,最後NPE
總結:
由上可知,cglib動態代理可以代理目標類非final和private方法,當呼叫final或者private方法時,由於目標類中不存在此方法,所以還是使用代理類進行呼叫。
下面我們可以進行原始碼debug,主要解決兩個問題:
- 為什麼會發生代理
- 代理類為啥屬性為空
原始碼debug
通常代理都是發生在Bean例項化完成之後,對成品的Bean進行代理,多發生在BeanProcess後置處理中
按照這個思路咱們開始走斷點debug:
-
例項化完成情況
我們發現例項化完成內部屬性是有引用值的,不等於null,所以問題不在這,往下看
2. 後置處理器

重點:從這裡我們發現Bean變成了代理物件,並且內部引用變成了null,證實了我們的猜想,由此可斷定問題出現在BeanProcess的後置處理中
-
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
跟隨斷點進入
AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
方法檢視
發現經歷了 AbstractAutoProxyCreator#postProcessAfterInitialization
方法後就發生了代理改變,我們繼續往下
-
AbstractAutoProxyCreator#wrapIfNecessary
在方法中
AbstractAutoProxyCreator#wrapIfNecessary
判斷了是否存在代理,此處生成了代理物件
在此處我們發現了因為aop切面存在,所以導致啟用了代理 問題一解決
-
代理生成
因為沒有介面,所以使用cglib代理
-
代理實現
這裡我們可以很清楚的看到是使用new構造生成出來的代理類,所以例項屬性值為空就解釋的通了, 問題二解決
總結:
由於AOP切面存在,導致目標類發生代理,生成了目標子類的代理Bean,代理類是通過 objenesis.newInstance(proxyClass, enhancer.getUseCache())
構造出來的,所以不存在相關屬性,聯絡到cglib代理原理---通過ASM位元組碼框架在執行期寫入位元組碼跳過了編譯期,可以佐證咱們的定論。
針對上面兩個問題結論如下:
- 由於方法切面導致目標類發生代理
- 代理類是在執行期通過構造new出來的,屬性值為空,所以代理類進行例項呼叫,會報NPE
我們對整個問題進行一個完整性總結:
由於AOP切面代理的原因,導致內部final方法呼叫走的代理類呼叫,代理類例項屬性為空,導致NPE。
模板頂層為抽象類,未實現介面,導致選擇cglib代理,cglib通過構造new實現代理類,內部屬性均為空,由於通過繼承實現,final和private方法無法被代理,所以當不可繼承方法被呼叫時,當前物件為代理類,否則為目標類。
解決方案
- 頂層實現介面,避免cglib代理
- 方法訪問修飾變更,可被繼承代理
- 手動getBean,指定目標類物件呼叫
在除錯的過程還發現一個有意思的現象: 整個引用呼叫鏈的方法棧上只要有一個方法被代理,呼叫鏈後端的所有方法都將使用目標類呼叫,不會導致NPE。 舉個例如下:invoke(final) -> myDo1(非final) -> myDo(final),此時不會產生NPE,因為這個時候執行Mydo方法的時候仍然是目標類。 有興趣的同學可以去翻一下原始碼,一起交流
附:代理類
從代理類上面我們可以看出:
- 代理類繼承具體子類
TestCglibAopExample
,所以final或者private相關方法,即Mydo()和invoke()方法代理類未提供實現,無法被代理。
獲取代理類class檔案命令,在idea啟動引數中新增
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
-Dcglib.debugLocation=/Users/xxx
關注我的公眾號一起交流吧!

- 執行緒池底層原理詳解與原始碼分析
- 30分鐘掌握 Webpack
- 線性迴歸大結局(嶺(Ridge)、 Lasso迴歸原理、公式推導),你想要的這裡都有
- 【前端必會】webpack loader 到底是什麼
- 中心化決議管理——雲端分析
- HashMap底層原理及jdk1.8原始碼解讀
- 詳解JS中 call 方法的實現
- 列印 Logger 日誌時,需不需要再封裝一下工具類?
- 初識設計模式 - 代理模式
- 密碼學奇妙之旅、01 CFB密文反饋模式、AES標準、Golang程式碼
- Springboot之 Mybatis 多資料來源實現
- CAS核心思想、底層實現
- 面試突擊86:SpringBoot 事務不回滾?怎麼解決?
- 基於electron vue element構建專案模板之【打包篇】
- MiniWord .NET Word模板引擎,藉由Word模板和資料簡單、快速生成檔案。
- 認識執行緒,初始併發
- 1-VSCode搭建GD32開發環境
- 初識設計模式 - 原型模式
- 執行緒安全問題的產生條件、解決方式
- 2>&1到底是什麼意思?