用debug分析SpringMVC源码

Debug 和 SpringMVC 的巅峰对决

学编程的人都知道, debug 是程序员离不开的调试工具, 用好debug 那是绝对可以使我们事半功倍。 这么说有点抽象, 比喻一下就是: debug 就好比九阳神功, 练成后天下武学皆附拾可用, 学什么都快。而debug 和其一样, 同属内功, 无论是简单代码分析, 还是算法调试, 亦或是追踪框架源码, 拥有深厚的 debug 功力, 绝对是如虎添翼, 所向披靡。 而使用这些个框架就仿佛简单的其他武学招式一般, 若神功在手, 那学起来是毫不费力的。话不多说, 本文就用 debug 来剖析一下 SpringMVC的一段源码, 从中来体会 debug 神功。

                                                                                     01

                                                                           要分析的代码

流程当我在地址栏输入 http://localhost:8080/项名/success.jsp 的时候,它将找到 Controller 后,先执行 @ModelAttribute 注解的方法,而且需要传入一个参数 id, 然后把 new 出来对象放入 map 中。最后执行 modelAttributeTest 方法,而这个参数显然是表单信息设置后的 user 对象(这个对象在放入map之后经历了什么?),然后执行成功,转发到 success.jsp 页面。

要分析的问题这个user对象中值是怎么改变的?在两个方法之间的是怎样进行传递的?@ModelAttribute在发挥的作用是什么?在底层源码中是如何完成这个过程的?并总结出流程,方便以后分析。

                                                                                      02

                                                                                  执行结果

                                                                               03

                                                                    debug分析过程

用debug来分析这个过程, 想要知道两个方法之间发生了什么,怎么下断点呢?可以在第一个方法在map.put(...) 下断点,因为要看这个user对象被放进来之后,到下个方法入参(要分析的环节)之间发生了什么。所以断点处就是以下两处:

现在的情况只能做到这一步,因为不知道框架如何具体是怎么执行的。接下来开启debug:

运行项目提交表单后,跳转到debug视图窗口并停在了17行:

既然执行到第17行,那之前肯定框架肯定是用反射在执行这个方法,通过堆栈可以看到,确实是这样:

定位到166行,打断点,并且要在它的下一行打一个断点,原因是要看到方法内变量的变化情况:

然后要做的是两次resume到目标方法内:

同理,找到框架是如何反射执行这个方法的:

定位到180行,既然是执行到第二个方法了,那么里面的参数肯定是被表单信息覆盖过的。从变量表args的值的信息中,可以印证我们的猜想。在180行打一个断点:

不要忘了我们的目的就是分析这个args怎么发生的变化,而获得args是在175,查一查这个方法是具体如何获取的,应该就能分析出一些细节,所以在此处打断点:

OK,那么我们现在可以尝试分析一下。刷新页面,重新提交表单,这个参数中第一个是处理器,我们分析第二个args,它的类型如下:

找到166所对应的方法,可以看到其中implicitModel所对应的值就是上图的入参bindingAwareModelMap

重要一步就要发生了,resume一次后:

再一次resume回到原来的invoke方法中,可以知道的是,166行执行了第一个方法,但不是我们分析的重点:

从变量表中可以看到invoke方法的参数args有值了,当map放入user对象,不再为空;而且implicitModel中也是一样.这里面明显放的是第一个参数中对象的值,那我们继续走,看一看它怎么变化的

执行175行

在执行完175行之后,参数的值发生了改变:

而导致改变的方法是resolveHandlerArguments方法,进去瞅一瞅这个方法,有一百几十行代码:

一行一行去读,显然是低效的。我们知道在表单为参数赋值的时候,肯定用到了setter方法,所以我们在bean中的set方法中打一个断点。重新提交表单,一直resume到setAccount方法:

重申一下我们要干什么,现在上面的过程中已经找到了具体的我们要分析环节的处理函数是哪个,但是由于代码行数多,我们需要给setter方法打上断点,因为resolveHandleArguments方法肯定调用了setter方法。当debug到setter方法时,就可以从栈中知道是resolveHandleArguments方法中哪行代码赋的值,如下图:

定位该行,并打上断点,方便之后分析:

既然setter方法发生在上图之中,那么设置完之后,肯定会封装成对象返回,step over继续,发现resolveHandlerArguments方法内,给args对象赋值代码如下:

从对象信息可知道,此时的信息已经改变。仔细分析代码,该对象是从binder的方法中获取的:

那么我们追根溯源,看看是什么时候创建的,往上翻一下,很容易就能找见,打断点:

重新提交执行到370会发现,参数是未变化的之前的。而dobind参数中存在这个binder,而这个方法会调用setter方法,所以就是在这个方法中对原先的user对象的值一一进行设置:

点进去这个方法,可以发现在780行创建了这个binder(下面有该方法全图和解释):

详细看一下createBinder方法,WebRequestDataBinder是继承了DataBinder,里面有两个属性:target和objectName,target我们上面知道了,是放的user对象,这两个参数对应上张图780行的后两个参数:

既然如此,那么我们看一看780行的两个参数具体是什么,怎么来的,在图中大致阐述了代码的意思,但需要注意的是,当map中没有放入user对象的话,那么在implicitModel中不存在key为user的对象的值,第一个if自然不会执行。从判断语句可以看出接下来如果标注了@SessionAttributes,且其中value和attrName匹配,会从session中获取attrName所对应属性值,如果获取不到的话将抛出异常。如果com.test.HandleTest中@SessionAttributes没有注解或者该注解中的值和attrName不匹配,那么就反射创建该对象:

延伸:为什么@ModelAttribute在标注方法参数时,如果不带("xxx"),指定对象名,会以参数中类名首字母小写来查找。此处已经很明了。如果@ModelAttribute中有值,则attrName为该值,不为空。如果没有,从第三张图可看到,类名首字母小写:

好,这个方法上面已经阐述的很明白了,resume到doBind方法,这个方法前面分析过,是将请求中的表单值赋值到原对象中:

binder这个对象中封装了target,在上面分析过了:

Step over:

Resume:

可以从变量表中看到,确实target变化了:

拿到target,返回args对象,并回到了之前的invoke方法:

拿到参数之后,反射执行第二个方法:

断点都执行完毕:

                                                                      04 

                                                                 总结流程

①首先找到@ModelAttribute注解方法,然后执行之。其中new了一个对象放入到了Map中,从源码中是存到了invokeHandlerMethod的implicitModel中。

②之后紧接着调用的是resolveHandlerArguments方法,大致过程如图中的1、2、3    步骤。步骤1在上面已经详细分析过了。

③ 得到了args,handlerMethodToInvoke反射执行第二个方法。

至此分析完毕,从过程中我们可以体会到合理的使用debug,可以锻炼我们的逻辑,帮助我们更容易地去驾驭和深入对框架的学习。

发布了73 篇原创文章 · 获赞 91 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/weixin_42512488/article/details/89420310