[bug]spring項目通過反射測試私有方法時,注入對象異常


背景

遇到問題:在進行Spring單元測試編寫時,發現被測方法是一個私有方法,無法直接通過注入對象調用
解決思路:首先想到通過反射獲取該私有方法的訪問權限,並傳入注入對象,最終調用對象的私有方法。

出現的異常

運行時拋出空指針異常
image

定位問題

  1. 點擊異常代碼行打上斷點,debug調試
    image
  2. 通過查看變量值發現roleMapper為空,從而導致空指針
  3. 而roleMapper是傳入this對象的屬性,因此,問題來自傳入的對象

分析問題

  1. 通過分析this對象,可以發現它是一個被Cglib代理后的實例,由此可知,該類方法上必定有@Transactional事務注解或AOP注解修飾,從而被SpringCglib代理
    image
  2. 查看cglib原理:

動態生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法。在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。它比使用java反射的JDK動態代理要快。

其中重要的一點,代理類是被代理類的子類,回想關於Java中的繼承,有一條很重要的特性就是:

  • 子類擁有父類非 private 的屬性、方法。
  1. 此時,嘗試修改私有方法變成public,發現this對象恢復正常,由此鎖定代理類和私有方法出現問題
    image
  2. 通過搜索cglib代理類私有方法發現原因:
    image
    image
  3. 由此可知,此處注入的cglib代理對象中不包含private方法!
  4. 那為啥同樣傳入的代理對象,調用public方法就成功,而調用private方法就失敗呢?
  1. 如果是私有方法,那么在代理類中,不會包含這個方法。此時通過Method.invoke()來調用目標方法,傳入的實例對象是userController的代理類,而這個代理類中的userService為NULL,所以,執行的時候,才會看到userService沒有注入,導致空指針異常。
  2. 如果是公共方法,在代理類中,就有它的子類實現,則會先調用到代理類的攔截器MethodInterceptor。攔截器負責鏈式調用AOP方法和目標方法。在攔截器執行過程中,又調用了方法。但不同的是,此時傳入的實例對象並不是代理類,而是代理類的目標對象。

結論:可以發現代理類正常情況下,執行到原方法時是通過代理的目標對象(即原始對象)來執行,而當代理類發現沒有代理對應的private方法時,則直接通過代理對象(即上文的this)執行目標方法。

解決方法

既然我們需要的是只原始對象執行私有方法,只要通過代理類獲取原始的目標對象即可。

// 由於cglib類是通過繼承代理,無法代理私有方法,因此無法通過原始對象執行方法
if (AopUtils.isCglibProxy(menuService)) {
    // 如果是cglib代理對象,則轉為原始對象
    menuService = (MenuServiceImpl)AopProxyUtils.getSingletonTarget(menuService);
}

此時得到的對象即為原始對象,bug成功消滅!
image

參考文章:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM