前言:最近一直在看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日,下午。