前言:最近一直在看Spring源碼,今天在調試的時候發現一個小問題:在註冊bean時,須要初始化spring默認命名空間處理器,具體在DefaultNamespaceHandlerResolver中實現,可是當Debug時,發現handlerMappings已經賦值,頓感奇怪。經過調試發現了該問題產生的緣由,遂記錄下來。spring
spring的代碼調用鏈很是的龐大,所以閱讀源碼的時候,也很是耗時,這裏給出建立DefaultNamespaceHandlerResolver的調用入口。安全
在上圖537行處打一斷點,運行後結果以下:app
注意this對象中拋出了異常,此時handlerMappings還爲null,this中拋出的異常信息以下:ide
此異常說明在Debug的時候,調用了toString()方法,但此時DefaultNamespaceHandlerResolver還未初始化完,因此拋出異常。猜想爲IDEA另起了一個線程調用了toStirng()方法。繼續調試代碼。函數
此時斷點在構造函數括號處,程序還未執行完,此時this對象處未拋異常了,handlerMappings還爲null。查看this中的信息。this
生成空間處理器的鍵值對。繼續調試程序,退出DefaultNamespaceHandlerResolver構造函數。idea
此時handlerMappings已經有9個值了,說明對其進行了初始化。根據上面的調試信息,查看DefaultNamespaceHandlerResolver的toString()方法。spa
可見在toString()方法中調用了getHandlerMappings方法。線程
注:該代碼是否是很熟悉,使用了Double-Check的方式避免非線程安全問題,爲單例模式的一種實現形式,是否是很神奇,spring源碼中應用了Double-Check。3d
當在DefaultNamespaceHandlerResolver初始化過程當中打斷點並利用IDEA進行調試的時候,IDEA會自動開啓一個線程調用該類的toString方法,在本例中就對handlerMappings進行了初始化;若是正常run的方式運行,是不會出現這種狀況的。
對於重寫了toString方法的類,在用Debug調試時會出現上述的狀況,可寫簡單代碼進行驗證,具體代碼以下:
1 public class ToStringTest { 2 /** 3 * 驗證Debug時,idea會開啓一個線程調用對象的toString方法 4 */ 5 public static void main(String[] args) { 6 7 WilltoStringInvoked will = new WilltoStringInvoked(); 8 9 System.out.println("若是在這裏設置斷點,則輸出1"); 10 11 System.out.println(will.getValue()); 12 13 System.out.println("若是不設置斷點,則輸出0"); 14 15 } 16 17 static class WilltoStringInvoked { 18 private volatile int value = 0; 19 20 private int setValue() { 21 if (value == 0) { 22 synchronized (this) { 23 if (value == 0) { 24 value = 1; 25 } 26 } 27 } 28 return value; 29 } 30 31 public int getValue() { 32 return value; 33 } 34 35 @Override 36 public String toString() { 37 return "This value is:" + setValue(); 38 } 39 } 40 }
在第9行處設置斷點,Debug結果以下:
若是不設置斷點,調試結果以下:
在調試spring源碼的時候,最開始出現該問題覺時以爲很難以想象,後面經過不斷的調試,猜想出該結論,並進行驗證;同時以爲spring真的很是強大,還需繼續努力,已經看了一段時間了,後面慢慢整理出來,增強印象與理解。
by Shawn Chen,2018.11.22日,下午。