1. Servlet
Servlet=Service+Applet
而Servlet的运行一般需要一个容器,java中常用的容器就是Tomcat。Servlet所做的工作就是在做了一层底层的Socket连接的封装,让我们可以不用管这些复杂的东西只关注于业务逻辑的处理。
感兴趣的可以看看Tomcat的底层原理
而我们知道SpringMVC的本质就是一个Servlet,但是我们要清楚其实整个过程是存在两步的:初始化+请求处理。
SpringMVC已经将初始化隐藏在底层,请求处理更是处理得简单到我们只需要考虑Get等请求。其实,初始化,请求处理才是我们源码学习的主要方向。
举个例子:Tomcat初始化时,同时Spring容器是怎么初始化的,Bean是如何加入到容器的?其次,请求处理从Tomcat到SpringMVC的DispatcherServlet是怎样?
围绕初始化+请求处理,就有了方向。搞清楚这些,基本上我们都懂得了大致的代码。
其次,则是在面对多种情况时,SpringMVC是如何处理的。我们看见了,高度依赖Spring容器的MVC采用了大量的设计模式,例如:贯穿SpringMVC的模板模式,适配器模式,组合模式等。这也是一个学习的方向。
对于SpringMVC的众多组件,在2020年前后端分离的大环境下,其实有很多组件都不需要再看了。比如theme等。
但是我们看源码的时候搞清楚了吗?
ControllerAdvice,RequestBody,ResponseBody是如何处理的?
模板引擎FreeMarker又是怎样的,这些都是问题。虽然目前我只看了一遍书籍与跟着学了学源码,但是仍然有许多问题疑惑,后面再来看源码的话, 就基本上是一些零碎问题的底层源码的理解了。
2. SpringMVC
初始化:HttpServletBean、FrameworkServlet和DispatcherServlet。
牢记这张图就可以明白,SpringMVC是如何联系上Tomcat的,从这个类图就可以看出,HttpServletBean是联系的纽带。
连接处理:
主要在DispatcherServlet中的processRequest方法:
①调用了doService模板方法具体处理请求,doService方法在DispatcherServlet中实现;
②将当前请求的LocaleContext和ServletRequestAttributes在处理请求前设置到了LocaleContextHolder和RequestContextHolder,并在请求处理完成后恢复;
③请求处理完后发布一个ServletRequestHandledEvent类型的消息。
举个例子:这里的问题为什么要拿到xxxHolder中?
以RequestContextHolder为例子:
从属性看到xxx类的Holder基本上字段和方法都是静态的,而且出现了ThreadLocal,立马就可以想到其是于线程绑定的一些属性可以通过全局拿到。
如何通过全局拿到呢?就是如下的静态方法:如何保证每次拿到的属性都是Tomcat线程相关的呢?ThreadLocal了解吗?
举一反三:XXXHolder在其他框架的使用。ThreadLocal了解。线程相关。
抛开框架我可以得到以上万金油。看完SpringMVC源码,当我再遇到其他框架时,很快就可以产生联想理解。
DispatcherServlet在doServic方法中将webApplicationContext、localeResolver、theme-Resolver、themeSource、FlashMap和FlashMapManager设置到request的属性中以方便使用,然后将请求交给doDispatch方法进行具体处理。
DispatcherServlet的doDispatch方法按执行过程大致可以分为4步:
①根据request找到Handler;
②根据找到的Handler找到对应的HandlerAdapter;
③用HandlerAdapter调用Handler处理请求;
④调用processDispatchResult方法处理Handler处理之后的结果;
等会我会来一次请求的debug来完成这次的学习。
下面来接着回顾:
Handler、HandlerMapping和HandlerAdapter这三者,其实在MVC中属于比较难的内容了,因为请求的多样性处理基本就在这儿。
这里我可以坦白:
但是没关系,目前仍然掌握全局。。。有闲心了再来看看就是了。
3. DeBug
还是围绕初始化,请求处理:
3.1 初始化
请注意:没有配置ContextLoaderListener
在HttpServletBean的init方法处打断点,然后启动tomcat:
org.springframework.web.servlet.HttpServletBean#init
来看看调用栈:
看看所处的包结构,是不是看出什么来了?
接着找到org.springframework.web.servlet.FrameworkServlet#initServletBean:
接下来怎么打断点?我也忘记了,没关系:
第一个: Step Over (F8):步过,一行一行地往下走,如果这一行上有方法不会进入方法。
蓝色 :Step Into (F7):步入,如果当前行有方法,可以进入方法内部,一般用于进入自定义方法内,不会进入官方类库的方法,如第25行的put方法。
红色:Force Step Into (Alt + Shift + F7):强制步入,能进入任何方法,查看底层源码的时候可以用这个进入官方类库的方法。
顾名思义,开始初始化Web容器:
此时是没有Web容器的,到这一步创建一个新的Web容器
看看容器有什么,其实就是Spring+Servlet的上下文,并加载入相应的Bean;
其中,DispatcherServlet根据contextConfigLocation配置的classpath下的xxxmvc.xml文件初始化了Spring MVC中的组件。这一步是在容器创建完成吼的那个方法内调用的。
随后刷新Web容器:
随后不断下一步,SpringMVC的内容就差不多完了。这里其实需要Spring的一些知识。
3.2 一个请求
首先我们人为的来模拟一次请求:
Tomcat收到请求后,应该是交给DispatcherServlet来处理,我们又知道Servlet顶层处理请求的方法是Service()。
那么我们来找DispatcherServlet的父类中谁有Service方法,很容易我们找到
org.springframework.web.servlet.FrameworkServlet#service
在processRequest()处打一个断点,继续跟踪。
org.springframework.web.servlet.FrameworkServlet#processRequest
为什么在doService处打断点?因为这是一个留给DispatcherServlet实现的模板方法。
那么我们进入DispatcherServlet:
org.springframework.web.servlet.DispatcherServlet#doService
在doDispatch做分发,进入doDispatch,org.springframework.web.servlet.DispatcherServlet#doDispatch
接着来启动,看看是否是这样,如何验证,其实很简单。一步一步玩下,或者直接只打最后一个断点,看调用栈。
发送一个请求:调用栈目前
我接着往下执行一步,其实出现了错误。这是个好现象。
调用栈调用的是:
其实是由
来处理是doGet还是doPost方法,前提是
请求方法是PATCH请求。很明显不是,那么就调用父类的Service()方法,然后由HttpServlet来处理请求,判断当前方法是Get请求,交由子类的FrameworkServlet的重载方法doGet来执行。
而我们之前人为的断点处理,是直接进入了processrequest方法,这样是不对的。
那么现在我们进入了doService:
继续往下执行:
再来看调用栈:
这个调用顺序就出来了。至此初始化+请求处理就完接了。其实学下来。掌握这个大体的流程就可以了。
4. SpringBoot
todo