前言:最近一直在看Spring源碼,今天在調試的時候發現一個小問題:在注冊bean時,需要初始化spring預設命名空間處理器,具體在DefaultNamespaceHandlerResolver中實作,但是當Debug時,發現handlerMappings已經指派,頓感奇怪。通過調試發現了該問題産生的原因,遂記錄下來。
1.調用入口
spring的代碼調用鍊非常的龐大,是以閱讀源碼的時候,也非常耗時,這裡給出建立DefaultNamespaceHandlerResolver的調用入口。

2.Debug時出現的現象
在上圖537行處打一斷點,運作後結果如下:
注意this對象中抛出了異常,此時handlerMappings還為null,this中抛出的異常資訊如下:
此異常說明在Debug的時候,調用了toString()方法,但此時DefaultNamespaceHandlerResolver還未初始化完,是以抛出異常。猜測為IDEA另起了一個線程調用了toStirng()方法。繼續調試代碼。
此時斷點在構造函數括号處,程式還未執行完,此時this對象處未抛異常了,handlerMappings還為null。檢視this中的資訊。
生成空間處理器的鍵值對。繼續調試程式,退出DefaultNamespaceHandlerResolver構造函數。
此時handlerMappings已經有9個值了,說明對其進行了初始化。根據上面的調試資訊,檢視DefaultNamespaceHandlerResolver的toString()方法。
可見在toString()方法中調用了getHandlerMappings方法。
注:該代碼是不是很熟悉,使用了Double-Check的方式避免非線程安全問題,為單例模式的一種實作形式,是不是很神奇,spring源碼中應用了Double-Check。
3.個人了解
當在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日,下午。